Apr. 25

Most of codes are based on:
https://realpython.com/get-started-with-django-1/

  1. # to check package XXX available
  2. yum provides XXX
  3. # previous commands
  4. history
  5. # python install
  6. pip install
  7. # Linux install
  8. yum install
  9. # sqlite3 version check
  10. sqlite3 --version
  1. - Made django into older version to make 'runserver' to lower it's requirement of sqlite3 version.
  2. ```bash
  3. pip uninstall django
  4. pip install Django==2.1.*


Apr. 26

  • According to the tutorial, created view and a basic “hello world” request listening
  • manipulated settings.py and url.py

Apr. 27

  • Template/foorstrap setup

Apr. 28

  • To create a database:
    • Used Object Relational Mapper (ORM) to modify database table models.py ```python from django.db import models

class Project(models.Model): title = models.CharField(max_length=100) description = models.TextField() technology = models.CharField(max_length=20) image = models.FilePathField(path=”/img”)

  1. - Created a migration based on the model, and applied that migrate
  2. ```python
  3. # Generated by Django 2.1.15 on 2020-04-29 03:39
  4. from django.db import migrations, models
  5. class Migration(migrations.Migration):
  6. initial = True
  7. dependencies = [
  8. ]
  9. operations = [
  10. migrations.CreateModel(
  11. name='Project',
  12. fields=[
  13. ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
  14. ('title', models.CharField(max_length=100)),
  15. ('description', models.TextField()),
  16. ('technology', models.CharField(max_length=20)),
  17. ('image', models.FilePathField(path='/img')),
  18. ],
  19. ),
  20. ]
  • db.sqlite3 is created and ready to go


  • To access Django shell:
    1. python manage.py shell
    Initialized 3 projects and created some related views.

Apr. 29 - May. 4

Change the currently project into a blog site featured web app.

Basic setup:

This part is based on video: https://www.youtube.com/watch?v=UmljXZIypDc

  • Get into the correct folder: ```bash [root@localhost Python]# django-admin startproject django_project

now we have

[root@localhost Python]# tree . └── djangoproject ├── djangoproject │ ├── __init.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── manage.py

  1. <a name="9ytnd"></a>
  2. #### To run the server:
  3. This part is based on video: [https://www.youtube.com/watch?v=UmljXZIypDc](https://www.youtube.com/watch?v=UmljXZIypDc)
  4. ```bash
  5. [root@localhost Python]# cd django_project/
  6. [root@localhost django_project]# python manage.py runserver
  7. Performing system checks...
  8. System check identified no issues (0 silenced).
  9. You have 15 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
  10. Run 'python manage.py migrate' to apply them.
  11. May 02, 2020 - 23:43:05
  12. Django version 2.1.15, using settings 'django_project.settings'
  13. Starting development server at http://127.0.0.1:8000/
  14. Quit the server with CONTROL-C.

Use localhost:8000/blog/ in the browser and we’ll see a page that shows
“The install worked successfully! Congratulations!”
…so that the server works. The terminal keeps tracing the server until you press “ctrl+c” or use some other methods to shutdown the server.

Add applications and routes:

This part is based on video: https://www.youtube.com/watch?v=a48xeeo5Vnk

  • The tutorial asked me to add an application “blog”, so that we start a blog here: ```bash [root@localhost django_project]# python manage.py startapp blog

now we have

[root@localhost djangoproject]# tree . ├── blog │ ├── admin.py │ ├── apps.py │ ├── init.py │ ├── migrations │ │ └── init.py │ ├── models.py │ ├── tests.py │ └── views.py ├── db.sqlite3 ├── djangoproject │ ├── init.py │ ├── pycache │ │ ├── __init.cpython-36.pyc │ │ ├── settings.cpython-36.pyc │ │ ├── urls.cpython-36.pyc │ │ └── wsgi.cpython-36.pyc │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── manage.py

4 directories, 17 files

  1. - build a webpage to handle /blog request
  2. - /blog/views.py:
  3. ```python
  4. from django.shortcuts import render
  5. from django.http import HttpResponse
  6. # handle the traffic from homepage to blog
  7. def home(request):
  8. return HttpResponse('<h1>Blog Home</h1>')
  • define a url pattern to that webpage
  • /blog/urls.py: ```python from django.urls import path from . import views # import from current directory

urlpatterns = [ path(‘’, views.home, name=’blog-home’), ]

  1. - Then redirect blog/ to that pattern
  2. - /django_project/urls.py
  3. ```python
  4. from django.contrib import admin
  5. from django.urls import path
  6. from django.conf.urls import include # new
  7. urlpatterns = [
  8. path('admin/', admin.site.urls),
  9. path('blog/', include('blog.urls')) # to blog/urls.py
  10. ]

To test:

Muitple routes:

Here we have a url localhost:8000/blog/about/

  • /blog/urls.py: ```python from django.urls import path from . import views # import from current directory

urlpatterns = [ path(‘’, views.home, name=’blog-home’), path(‘about/‘, views.about, name=’blog-about’), ]

  1. - /blog/views.py
  2. ```python
  3. from django.shortcuts import render
  4. from django.http import HttpResponse
  5. def home(request):
  6. return HttpResponse('<h1>Blog Home</h1>')
  7. # new
  8. def about(request):
  9. return HttpResponse('<h1>Blog About</h1>')

To test, input localhost:8000/blog/about/ in a browser.
Now we can see a header “Blog About” instead of “Blog Home”, it means that the pattern is directed to def about in /django_project/urls.py instead of def home.

  • To make blog as the homepage of the app, change /django_project/urls.py into: ```python from django.contrib import admin from django.urls import path from django.conf.urls import include # new

urlpatterns = [ path(‘admin/‘, admin.site.urls),

  1. # path('blog/', include('blog.urls'))
  2. path('', include('blog.urls')) # change here

]

  1. Now you only need [localhost:8000](http://localhost:8000/blog/about/) to browser the homepage.
  2. <a name="MrGzJ"></a>
  3. #### Templates:
  4. - add an app in django_project/settings.py:
  5. ```python
  6. //...
  7. INSTALLED_APPS = [
  8. 'blog.apps.BlogConfig', # new
  9. 'django.contrib.admin',
  10. 'django.contrib.auth',
  11. 'django.contrib.contenttypes',
  12. 'django.contrib.sessions',
  13. 'django.contrib.messages',
  14. 'django.contrib.staticfiles',
  15. ]
  16. //...
  • create 2 directories to save templates:

    1. [root@localhost django_project]# mkdir blog/templates
    2. [root@localhost django_project]# mkdir blog/templates/blog
  • make a basic HTML page for home called home.html in blog/templates/blog/

    1. <!DOCTYPE html>
    2. <html>
    3. <head>
    4. <title></title>
    5. </head>
    6. <body>
    7. <h1>Blog Home!</h1>
    8. </body>
    9. </html>
  • change blog/views.py to redirect the request to the HTML page we just setup: ```python from django.shortcuts import render from django.http import HttpResponse

handle the traffic from homepage to blog

def home(request): return render(request, ‘blog/home.html’)

  1. #return HttpResponse('<h1>Blog Home</h1>')

def about(request): return HttpResponse(‘

Blog About

‘)

  1. To test:
  2. - [localhost:8000/](http://127.0.0.1:8000/blog/) in a browser to see "Blog Home!", same as blog/templates/blog/home.html
  3. We can use similar way to set a template and apply it for "about":
  4. - make a basic HTML page for about called about.html in blog/templates/blog/
  5. ```html
  6. <!DOCTYPE html>
  7. <html>
  8. <head>
  9. <title></title>
  10. </head>
  11. <body>
  12. <h1>Blog About!</h1>
  13. </body>
  14. </html>
  • change blog/views.py to redirect the request to the HTML page we just setup: ```python from django.shortcuts import render from django.http import HttpResponse

handle the traffic from homepage to blog

def home(request): return render(request, ‘blog/home.html’)

  1. #return HttpResponse('<h1>Blog Home</h1>')

def about(request): return render(request, ‘blog/about.html’)

  1. # return HttpResponse('<h1>Blog About</h1>')
  1. To test:
  2. - [localhost:8000/blog/](http://127.0.0.1:8000/blog/) in a browser to see "Blog About!"
  3. - To handle more stuffs, here we have a 'post' dictionary in blog/views.py:
  4. ```python
  5. //...
  6. posts = [
  7. {
  8. 'author': 'CoreyMS',
  9. 'title': 'Blog Post 1',
  10. 'content': 'First post content',
  11. 'date_posted': 'August 27, 2018'
  12. },
  13. {
  14. 'author': 'Jane Doe',
  15. 'title': 'Blog Post 2',
  16. 'content': 'Second post content',
  17. 'date_posted': 'August 28, 2018'
  18. }
  19. ]
  20. //...
  • we want to post them, so we adjust blog/templates/blog/home.html:

    1. <!DOCTYPE html>
    2. <html>
    3. <head>
    4. <title></title>
    5. </head>
    6. <body>
    7. {% for post in posts %}
    8. <h1>{{ post.title }}</h1>
    9. <p>By{{ post.author }} on {{ post.date_posted }}</p>
    10. <p>{{ post.content }}</p>
    11. {% endfor %}
    12. </body>
    13. </html>

    and now we have a HTML file with a for loop, so that it can recursively print posts in sequence.
    To test:

  • localhost:8000/ in a browser

we see:

Capture.JPG

  • Here we do another test, pass dictionary and apply if else in blog/templates/blog/about.html:

    1. <!DOCTYPE html>
    2. <html>
    3. <head>
    4. {% if title %}
    5. <title>Django Blog - {{ title }}</title>
    6. {% else %}
    7. <title>Django Blog</title>
    8. {% endif %}
    9. </head>
    10. <body>
    11. <h1>Blog About!</h1>
    12. </body>
    13. </html>
  • in blog/views.py:

    1. def about(request):
    2. return render(request, 'blog/about.html', {'title': 'About'})
    3. # return render(request, 'blog/about.html')

    To test:

  • localhost:8000/about/ in a browser

If there is {‘title’: ‘About’} in render, the title(not header) of that page is “Django Blog - About”
If not, the title is “Django Blog”

We also apply that if else express of title to blog/templates/blog/home.html:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. {% if title %}
  5. <title>Django Blog - {{ title }}</title>
  6. {% else %}
  7. <title>Django Blog</title>
  8. {% endif %}
  9. </head>
  10. <body>
  11. {% for post in posts %}
  12. <h1>{{ post.title }}</h1>
  13. <p>By{{ post.author }} on {{ post.date_posted }}</p>
  14. <p>{{ post.content }}</p>
  15. {% endfor %}
  16. </body>
  17. </html>

and now we have an appropriate title for both pages.

Inherit templates:

We can add another HTML file to make sure more codes are reused.

  • Create a base.html in blog/templates/blog/
  • Add common parts and some available starter templates:

    1. <!DOCTYPE html>
    2. <html>
    3. <head>
    4. <!-- Required meta tags -->
    5. <meta charset="utf-8">
    6. <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    7. <!-- Bootstrap CSS -->
    8. <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
    9. {% if title %}
    10. <title>Django Blog - {{ title }}</title>
    11. {% else %}
    12. <title>Django Blog</title>
    13. {% endif %}
    14. </head>
    15. <body>
    16. <div class = "container">
    17. {% block content %}{% endblock %}
    18. </div>
    19. <!-- Optional JavaScript -->
    20. <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    21. <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
    22. <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
    23. <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
    24. </body>
    25. </html>
  • use this “extends content” logic on home.html and about.html:

    1. {% extends "blog/base.html" %}
    2. {% block content %}
    3. {% for post in posts %}
    4. <h1>{{ post.title }}</h1>
    5. <p>By{{ post.author }} on {{ post.date_posted }}</p>
    6. <p>{{ post.content }}</p>
    7. {% endfor %}
    8. {% endblock content %}
    1. {% extends "blog/base.html" %}
    2. {% block content %}
    3. <h1>Blog About!</h1>
    4. {% endblock content %}

    They will show the same content as those versions which did not apply base.html
    Use view-source:(path) to check it.

May. 5

Add navigation & main template:

https://github.com/CoreyMSchafer/code_snippets/blob/master/Django_Blog/snippets
Copy navigation.html and main.html in this link to base.html:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <!-- Required meta tags -->
  5. <meta charset="utf-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  7. <!-- Bootstrap CSS -->
  8. <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
  9. {% if title %}
  10. <title>Django Blog - {{ title }}</title>
  11. {% else %}
  12. <title>Django Blog</title>
  13. {% endif %}
  14. </head>
  15. <body>
  16. <!-- navigation.html -->
  17. <header class="site-header">
  18. <nav class="navbar navbar-expand-md navbar-dark bg-steel fixed-top">
  19. <div class="container">
  20. <a class="navbar-brand mr-4" href="/">Django Blog</a>
  21. <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarToggle" aria-controls="navbarToggle" aria-expanded="false" aria-label="Toggle navigation">
  22. <span class="navbar-toggler-icon"></span>
  23. </button>
  24. <div class="collapse navbar-collapse" id="navbarToggle">
  25. <div class="navbar-nav mr-auto">
  26. <a class="nav-item nav-link" href="/">Home</a>
  27. <a class="nav-item nav-link" href="/about">About</a>
  28. </div>
  29. <!-- Navbar Right Side -->
  30. <div class="navbar-nav">
  31. <a class="nav-item nav-link" href="#">Login</a>
  32. <a class="nav-item nav-link" href="#">Register</a>
  33. </div>
  34. </div>
  35. </div>
  36. </nav>
  37. </header>
  38. <!-- main.html -->
  39. <main role="main" class="container">
  40. <div class="row">
  41. <div class="col-md-8">
  42. {% block content %}{% endblock %}
  43. </div>
  44. <div class="col-md-4">
  45. <div class="content-section">
  46. <h3>Our Sidebar</h3>
  47. <p class='text-muted'>You can put any information here you'd like.
  48. <ul class="list-group">
  49. <li class="list-group-item list-group-item-light">Latest Posts</li>
  50. <li class="list-group-item list-group-item-light">Announcements</li>
  51. <li class="list-group-item list-group-item-light">Calendars</li>
  52. <li class="list-group-item list-group-item-light">etc</li>
  53. </ul>
  54. </p>
  55. </div>
  56. </div>
  57. </div>
  58. </main>
  59. <!-- Optional JavaScript -->
  60. <!-- jQuery first, then Popper.js, then Bootstrap JS -->
  61. <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
  62. <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
  63. <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
  64. </body>
  65. </html>

Now we have a navigation bar for each page.

May. 6

Load static:

  • Create a folder under blog/ called static/
  • Then, create another folder called blog/ under blog/static/
  • Copy https://github.com/CoreyMSchafer/code_snippets/blob/master/Django_Blog/snippets/main.css to blog/static/blog/
  • Now the project looks like: ```bash [root@localhost djangoproject]# tree . ├── blog │ ├── admin.py │ ├── apps.py │ ├── init.py │ ├── migrations │ │ ├── init.py │ │ └── pycache │ │ └── init.cpython-36.pyc │ ├── models.py │ ├── pycache │ │ ├── admin.cpython-36.pyc │ │ ├── apps.cpython-36.pyc │ │ ├── init.cpython-36.pyc │ │ ├── models.cpython-36.pyc │ │ ├── urls.cpython-36.pyc │ │ └── views.cpython-36.pyc │ ├── static │ │ └── blog │ │ └── main.css │ ├── templates │ │ └── blog │ │ ├── about.html │ │ ├── base.html │ │ └── home.html │ ├── tests.py │ ├── urls.py │ └── views.py ├── db.sqlite3 ├── djangoproject │ ├── init.py │ ├── pycache │ │ ├── __init.cpython-36.pyc │ │ ├── settings.cpython-36.pyc │ │ ├── urls.cpython-36.pyc │ │ └── wsgi.cpython-36.pyc │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── manage.py

10 directories, 29 files

  1. - Let templates/blog/base.html apply that css file:
  2. ```html
  3. <!-- new -->
  4. {% load static %}
  5. <!DOCTYPE html>
  6. <html>
  7. <head>
  8. <meta charset="utf-8">
  9. <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  10. <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
  11. <!-- load blog/main.css -->
  12. <link rel="stylesheet" type="text/css" href="{% static 'blog/main.css' %}">
  13. {% if title %}
  14. <title>Django Blog - {{ title }}</title>
  15. {% else %}
  16. <title>Django Blog</title>
  17. {% endif %}
  18. </head>
  19. <body>
  20. <header class="site-header">
  21. <nav class="navbar navbar-expand-md navbar-dark bg-steel fixed-top">
  22. <div class="container">
  23. <a class="navbar-brand mr-4" href="/">Django Blog</a>
  24. <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarToggle" aria-controls="navbarToggle" aria-expanded="false" aria-label="Toggle navigation">
  25. <span class="navbar-toggler-icon"></span>
  26. </button>
  27. <div class="collapse navbar-collapse" id="navbarToggle">
  28. <div class="navbar-nav mr-auto">
  29. <a class="nav-item nav-link" href="/">Home</a>
  30. <a class="nav-item nav-link" href="/about">About</a>
  31. </div>
  32. <!-- Navbar Right Side -->
  33. <div class="navbar-nav">
  34. <a class="nav-item nav-link" href="#">Login</a>
  35. <a class="nav-item nav-link" href="#">Register</a>
  36. </div>
  37. </div>
  38. </div>
  39. </nav>
  40. </header>
  41. <main role="main" class="container">
  42. <div class="row">
  43. <div class="col-md-8">
  44. {% block content %}{% endblock %}
  45. </div>
  46. <div class="col-md-4">
  47. <div class="content-section">
  48. <h3>Our Sidebar</h3>
  49. <p class='text-muted'>You can put any information here you'd like.
  50. <ul class="list-group">
  51. <li class="list-group-item list-group-item-light">Latest Posts</li>
  52. <li class="list-group-item list-group-item-light">Announcements</li>
  53. <li class="list-group-item list-group-item-light">Calendars</li>
  54. <li class="list-group-item list-group-item-light">etc</li>
  55. </ul>
  56. </p>
  57. </div>
  58. </div>
  59. </div>
  60. </main>
  61. <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
  62. <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
  63. <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
  64. </body>
  65. </html>

Now the page looks like:
Capture.JPG

Article format:

  • Copy codes in https://github.com/CoreyMSchafer/code_snippets/blob/master/Django_Blog/snippets/article.html
  • Delete the for loop in templates/blog/home.html and add these lines to show articles(posts) better:
    1. {% extends "blog/base.html" %}
    2. {% block content %}
    3. {% for post in posts %}
    4. <!-- new -->
    5. <article class="media content-section">
    6. <div class="media-body">
    7. <div class="article-metadata">
    8. <a class="mr-2" href="#">{{ post.author }}</a>
    9. <small class="text-muted">{{ post.date_posted }}</small>
    10. </div>
    11. <h2><a class="article-title" href="#">{{ post.title }}</a></h2>
    12. <p class="article-content">{{ post.content }}</p>
    13. </div>
    14. </article>
    15. {% endfor %}
    16. {% endblock content %}
    Now the page looks like:
    Capture.JPG

    Adjust site-header:

    1. <header class="site-header">
    2. <nav class="navbar navbar-expand-md navbar-dark bg-steel fixed-top">
    3. <div class="container">
    4. <!-- href="/" -->
    5. <a class="navbar-brand mr-4" href="{% url 'blog-home' %}">Django Blog</a>
    6. <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarToggle" aria-controls="navbarToggle" aria-expanded="false" aria-label="Toggle navigation">
    7. <span class="navbar-toggler-icon"></span>
    8. </button>
    9. <div class="collapse navbar-collapse" id="navbarToggle">
    10. <div class="navbar-nav mr-auto">
    11. <!-- href="/" -->
    12. <a class="nav-item nav-link" href="{% url 'blog-home' %}">Home</a>
    13. <!-- href="/about" -->
    14. <a class="nav-item nav-link" href="{% url 'blog-about' %}">About</a>
    15. </div>
    16. <!-- Navbar Right Side -->
    17. <div class="navbar-nav">
    18. <a class="nav-item nav-link" href="#">Login</a>
    19. <a class="nav-item nav-link" href="#">Register</a>
    20. </div>
    21. </div>
    22. </div>
    23. </nav>
    24. </header>
    so that those button on the header can refer to names in blog/urls.py instead of a route: ```python

    blog/urls.py

    from django.urls import path from . import views # import from current directory

urlpatterns = [ path(‘’, views.home, name=’blog-home’), path(‘about/‘, views.about, name=’blog-about’), ]

  1. <a name="wS9Il"></a>
  2. #### Admin page:
  3. This step we gonna make that localhost:8000/admin/ useful by adding an admin.
  4. ```bash
  5. [root@localhost django_project]# python manage.py migrate
  6. Operations to perform:
  7. Apply all migrations: admin, auth, contenttypes, sessions
  8. Running migrations:
  9. Applying contenttypes.0001_initial... OK
  10. Applying auth.0001_initial... OK
  11. Applying admin.0001_initial... OK
  12. Applying admin.0002_logentry_remove_auto_add... OK
  13. Applying admin.0003_logentry_add_action_flag_choices... OK
  14. Applying contenttypes.0002_remove_content_type_name... OK
  15. Applying auth.0002_alter_permission_name_max_length... OK
  16. Applying auth.0003_alter_user_email_max_length... OK
  17. Applying auth.0004_alter_user_username_opts... OK
  18. Applying auth.0005_alter_user_last_login_null... OK
  19. Applying auth.0006_require_contenttypes_0002... OK
  20. Applying auth.0007_alter_validators_add_error_messages... OK
  21. Applying auth.0008_alter_user_username_max_length... OK
  22. Applying auth.0009_alter_user_last_name_max_length... OK
  23. Applying sessions.0001_initial... OK
  24. [root@localhost django_project]# python manage.py createsuperuser
  25. Username (leave blank to use 'root'):
  26. Email address:
  27. Password:
  28. Password (again):
  29. Superuser created successfully.

To test, login on localhost:8000/admin/ with the admin account you just created, and you will see the default Django site admin page:
Capture.JPG

  • Enter “Users” page, we click that “new user” button:

Capture.JPG

  • Setup a test user for later tests:

Capture.JPG

  • We click “save and continue editing” for further setup of this non-admin account, such as email address and superuser status, etc.
  • There are too many detailed selections, so we don’t show this step.
  • Now we have 2 accounts for this website.

May. 7

Database & migrations:

  • add a “Post” class in blog/models.py ```go from django.db import models from django.utils import timezone from django.contrib.auth.models import User

class Post(models.Model): title = models.CharField(max_length=100) content = models.TextField() date_posted = models.DateField(default=timezone.now) author = models.ForeignKey(User, on_delete=models.CASCADE)

  1. def __str__(self):
  2. return self.title
  1. - in the terminal, use makemigrations to create a migration:
  2. ```bash
  3. [root@localhost django_project]# python manage.py makemigrations
  4. Migrations for 'blog':
  5. blog/migrations/0001_initial.py
  • that initiated blog/migrations/0001_initial.py looks like: ```go from django.conf import settings from django.db import migrations, models import django.db.models.deletion import django.utils.timezone

class Migration(migrations.Migration):

  1. initial = True
  2. dependencies = [
  3. migrations.swappable_dependency(settings.AUTH_USER_MODEL),
  4. ]
  5. operations = [
  6. migrations.CreateModel(
  7. name='Post',
  8. fields=[
  9. ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
  10. ('title', models.CharField(max_length=100)),
  11. ('content', models.TextField()),
  12. ('date_posted', models.DateField(default=django.utils.timezone.now)),
  13. ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
  14. ],
  15. ),
  16. ]
  1. - Now create a "blog_post" SQL table and migrate:
  2. ```bash
  3. [root@localhost django_project]# python manage.py sqlmigrate blog 0001
  4. BEGIN;
  5. --
  6. -- Create model Post
  7. --
  8. CREATE TABLE "blog_post" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "title" varchar(100) NOT NULL, "content" text NOT NULL, "date_posted" date NOT NULL, "author_id" integer NOT NULL REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED);
  9. CREATE INDEX "blog_post_author_id_dd7a8485" ON "blog_post" ("author_id");
  10. COMMIT;
  11. [root@localhost django_project]# python manage.py migrate
  12. Operations to perform:
  13. Apply all migrations: admin, auth, blog, contenttypes, sessions
  14. Running migrations:
  15. Applying blog.0001_initial... OK
  • add an object into Post model in python shell: ```bash [root@localhost django_project]# python manage.py shell Python 3.6.5 (default, Sep 10 2018, 09:39:42) Type ‘copyright’, ‘credits’ or ‘license’ for more information IPython 6.5.0 — An enhanced Interactive Python. Type ‘?’ for help.

In [1]: from blog.models import Post

In [2]: from django.contrib.auth.models import User

In [3]: User.objects.all() Out[3]: , ]>

In [4]: User.objects.first() Out[4]:

In [5]: User.objects.filter(username = “root”) Out[5]: ]>

In [6]: User.objects.filter(username = “root”).first() Out[6]:

In [7]: user = User.objects.filter(username = “root”).first()

In [8]: user Out[8]:

In [9]: user.id Out[9]: 1

In [10]: user.pk # primary key Out[10]: 1

In [12]: user = User.objects.get(id=1)

In [13]: user Out[13]:

In [14]: Post.objects Out[14]:

In [15]: Post.objects.all() Out[15]:

In [16]: post_1 = Post(title=’Blog’, content=’First Post Content!’, author=user)

In [17]: post_1.save()

In [18]: Post.objects.all() Out[18]: ]>

  1. - Here shows several methods to see variables in a model:
  2. ```bash
  3. [root@localhost django_project]# python manage.py shell
  4. Python 3.6.5 (default, Sep 10 2018, 09:39:42)
  5. Type 'copyright', 'credits' or 'license' for more information
  6. IPython 6.5.0 -- An enhanced Interactive Python. Type '?' for help.
  7. In [1]: from blog.models import Post
  8. In [2]: from django.contrib.auth.models import User
  9. In [3]: Post.objects.all()
  10. Out[3]: <QuerySet [<Post: Blog>]>
  11. In [4]: User.objects.filter(username = "root").first()
  12. Out[4]: <User: root>
  13. In [5]: user = User.objects.filter(username = "root").first()
  14. In [6]: user
  15. Out[6]: <User: root>
  16. In [7]: post_2 = Post(title='Blog 2', content='Second Post Content!', author_id=
  17. ...: user.id)
  18. In [8]: post_2.save()
  19. In [9]: Post.objects.all()
  20. Out[9]: <QuerySet [<Post: Blog>, <Post: Blog 2>]>
  21. In [10]: post = Post.objects.first()
  22. In [11]: post.content
  23. Out[11]: 'First Post Content!'
  24. In [12]: post.date_posted
  25. Out[12]: datetime.date(2020, 5, 7)
  26. In [13]: post.author
  27. Out[13]: <User: root>
  28. In [14]: post.author.email
  29. Out[14]: 'root@gmail.com'
  30. In [15]: user.post_set
  31. Out[15]: <django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager.<locals>.RelatedManager at 0x7ff1f05a3c88>
  32. In [16]: user.post_set.all()
  33. Out[16]: <QuerySet [<Post: Blog>, <Post: Blog 2>]>
  34. In [17]: user.post_set.create(title='Blog 3', content = 'Third Post Content!')
  35. Out[17]: <Post: Blog 3>
  36. In [18]: Post.objects.all()
  37. Out[18]: <QuerySet [<Post: Blog>, <Post: Blog 2>, <Post: Blog 3>]>
  38. In [19]: Post.objects.all().first()
  39. Out[19]: <Post: Blog>
  • Now we have a table and several objects, so we can just change the way to post in blog/views.py: ```go from django.shortcuts import render from .models import Post #new

def home(request): context = { ‘posts’: Post.objects.all() #import from blog_post }

  1. return render(request, 'blog/home.html', context)

def about(request): return render(request, ‘blog/about.html’, {‘title’: ‘About’})

  1. Then we see posts are replaced into stuff in the database:<br />![Capture.JPG](https://cdn.nlark.com/yuque/0/2020/jpeg/1243266/1588908245812-c5f35c61-588f-4f76-b21a-e82fd2d77a66.jpeg#align=left&display=inline&height=790&margin=%5Bobject%20Object%5D&name=Capture.JPG&originHeight=790&originWidth=1908&size=69595&status=done&style=none&width=1908)
  2. <a name="Hzwu4"></a>
  3. #### Update posts:
  4. - in blog/admin.py:
  5. ```go
  6. from django.contrib import admin
  7. from .models import Post
  8. admin.site.register(Post)
  • And now we have a link to view and update posts in localhost:8000/admin:

Capture.JPGCapture2.JPG

May. 8

User registeration:

This time we add a user register system.

  • First, start a new app called users, and now we have a users/ directory: ```bash [root@localhost djangoproject]# python manage.py startapp users [root@localhost djangoproject]# tree . ├── blog │ ├── admin.py │ ├── apps.py │ ├── init.py │ ├── migrations │ │ ├── 0001initial.py │ │ ├── init.py │ │ └── pycache │ │ ├── 0001initial.cpython-36.pyc │ │ └── init.cpython-36.pyc │ ├── models.py │ ├── pycache │ │ ├── admin.cpython-36.pyc │ │ ├── apps.cpython-36.pyc │ │ ├── init.cpython-36.pyc │ │ ├── models.cpython-36.pyc │ │ ├── urls.cpython-36.pyc │ │ └── views.cpython-36.pyc │ ├── static │ │ └── blog │ │ └── main.css │ ├── templates │ │ └── blog │ │ ├── about.html │ │ ├── base.html │ │ └── home.html │ ├── tests.py │ ├── urls.py │ └── views.py ├── db.sqlite3 ├── djangoproject │ ├── init.py │ ├── pycache │ │ ├── _init.cpython-36.pyc │ │ ├── settings.cpython-36.pyc │ │ ├── urls.cpython-36.pyc │ │ └── wsgi.cpython-36.pyc │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── manage.py └── users ├── admin.py ├── apps.py ├── __init.py ├── migrations │ └── __init.py ├── models.py ├── tests.py └── views.py

12 directories, 38 files

  1. - change django_project/settings.py:
  2. ```go
  3. //...
  4. INSTALLED_APPS = [
  5. 'blog.apps.BlogConfig',
  6. 'users.apps.UsersConfig', # new
  7. 'django.contrib.admin',
  8. 'django.contrib.auth',
  9. 'django.contrib.contenttypes',
  10. 'django.contrib.sessions',
  11. 'django.contrib.messages',
  12. 'django.contrib.staticfiles',
  13. ]
  14. //...
  • add a view in users/views.py: ```go from django.shortcuts import render from django.contrib.auth.forms import UserCreationForm

def register(request): form = UserCreationForm() return render(request, ‘users/register.html’, {‘form’: form})

  1. - make a templates/ folder in users/ and users. in users/templates/
  2. - create a register.html file in template/users/
  3. ```html
  4. {% extends "blog/base.html" %}
  5. {% block content %}
  6. <div class="content-section">
  7. <form method="POST">
  8. {% csrf_token %}
  9. <fieldset class="form-group">
  10. <legend class="border-bottom mb-4">Join Today</legend>
  11. {{ form }}
  12. </fieldset>
  13. <div class="form-group">
  14. <button class="btn btn-outline-info" type="submit">Sign Up</button>
  15. </div>
  16. </form>
  17. <div class="border-top pt-3">
  18. <small class="text-muted">
  19. Already Have An Account? <a class="ml-2" href="#">Sign In</a>
  20. </small>
  21. </div>
  22. </div>
  23. {% endblock content %}

{% csrf_token %} is added for safety concern, and

includes an automatic method to create an account.

  • update django_project/urls.py: ```go from django.contrib import admin from django.urls import path from django.conf.urls import include from users import views as user_views # new

urlpatterns = [ path(‘admin/‘, admin.site.urls), path(‘register/‘, user_views.register, name = ‘register’), # new path(‘’, include(‘blog.urls’)) ]

  1. To test, run the server and use the path of register/ to get: <br />![Capture.JPG](https://cdn.nlark.com/yuque/0/2020/jpeg/1243266/1588969495436-7232fc64-b5b7-4c63-a576-317a7d60e0d5.jpeg#align=left&display=inline&height=653&margin=%5Bobject%20Object%5D&name=Capture.JPG&originHeight=653&originWidth=1492&size=77017&status=done&style=none&width=1492)<br />If we change {{ form }} in "form-group" class into {{ form.as_p }}, lines spilts a bit: <br />![Capture.JPG](https://cdn.nlark.com/yuque/0/2020/jpeg/1243266/1588969668075-5de8dbfb-3c4f-49e5-bb69-47585ac361c2.jpeg#align=left&display=inline&height=704&margin=%5Bobject%20Object%5D&name=Capture.JPG&originHeight=704&originWidth=1431&size=77753&status=done&style=none&width=1431)
  2. - add a if else logic for users/views.py to handle POST or non-POST requests:
  3. ```go
  4. from django.shortcuts import render
  5. from django.contrib.auth.forms import UserCreationForm
  6. from django.contrib import messages
  7. def register(request):
  8. if request.method == 'POST':
  9. form = UserCreationForm(request.POST)
  10. if form.is_valid():
  11. form.save()
  12. username = form.cleaned_data.get('username')
  13. messages.success(request, f'Account created for {username}!')
  14. return redirect('blog-home')
  15. else:
  16. form = UserCreationForm()
  17. return render(request, 'users/register.html', {'form': form})
  • and add several lines for blog/templates/blog/base.html to display messages:

    1. <!--...-->
    2. <div class="col-md-8">
    3. {% if messages %}
    4. {% for message in messages %}
    5. <div class="alert alert-{{ message.tags }}">
    6. {{ message }}
    7. </div>
    8. {% endfor %}
    9. {% endif %}
    10. {% block content %}{% endblock %}
    11. </div>
    12. <!--...-->
  • after that, the register page can tell you some common errors such as “A user with that username already exists.” or “The two password fields didn’t match.”, etc.

  • If username and password pair is valid, a new user is successfully created.

Change registration mode:

  • Becuase the default register only includes username and password, here we create forms.py under /users to transmit UserCreationForm into a new form: ```go from django import forms from django.contrib.auth.models import User from django.contrib.auth.forms import UserCreationForm

class UserRegisterForm(UserCreationForm): email = forms.EmailField()

  1. class Meta:
  2. model = User
  3. fields = ['username', 'email', 'password1', 'password2']
  1. - Then we discard UserCreationForm, apply UserRegisterForm in users/views.py:
  2. ```go
  3. from django.shortcuts import render, redirect #new: redirect
  4. # from django.contrib.auth.forms import UserCreationForm
  5. from django.contrib import messages
  6. from .forms import UserRegisterForm
  7. def register(request):
  8. if request.method == 'POST':
  9. form = UserRegisterForm(request.POST)
  10. if form.is_valid():
  11. form.save()
  12. username = form.cleaned_data.get('username')
  13. messages.success(request, f'Account created for {username}!')
  14. return redirect('blog-home')
  15. else:
  16. form = UserRegisterForm()
  17. return render(request, 'users/register.html', {'form': form})

Now we try to register:
Capture.JPG
and now we have a register page that requires email address.

Import crispy form:

“Django-crispy-forms is an application that helps to manage Django forms. It allows adjusting forms’ properties (such as method, send button or CSS classes) on the backend without having to re-write them in the template.”

  • install crispy form:

    1. pip install django-crispy-forms
  • After the install is complete, change django_project/settings.py:

  • (remember that, most of complicated functions are done by those installed apps, and the first thing we should do is to include it in the list of “INSTALLED_APPS”) ```go //… INSTALLED_APPS = [ ‘blog.apps.BlogConfig’, ‘users.apps.UsersConfig’, ‘crispy_forms’, #new ‘django.contrib.admin’, ‘django.contrib.auth’, ‘django.contrib.contenttypes’, ‘django.contrib.sessions’, ‘django.contrib.messages’, ‘django.contrib.staticfiles’, ] //…

CRISPY_TEMPLATE_PACK = ‘bootstrap4’

  1. Then registers.html:
  2. ```html
  3. {% extends "blog/base.html" %}
  4. {% load crispy_forms_tags %} # new
  5. {% block content %}
  6. <div class="content-section">
  7. <form method="POST">
  8. {% csrf_token %}
  9. <fieldset class="form-group">
  10. <legend class="border-bottom mb-4">Join Today</legend>
  11. {{ form|crispy }} # new
  12. </fieldset>
  13. <div class="form-group">
  14. <button class="btn btn-outline-info" type="submit">Sign Up</button>
  15. </div>
  16. </form>
  17. <div class="border-top pt-3">
  18. <small class="text-muted">
  19. Already Have An Account? <a class="ml-2" href="#">Sign In</a>
  20. </small>
  21. </div>
  22. </div>
  23. {% endblock content %}

Now the register page looks better:
Capture.JPG

May. 11

  • This time we want to add a pair of pages to login and logout instead of using http://localhost:port/admin/.

    Login System:

  • add new paths in django_project/urls.py: ```python from django.contrib import admin from django.urls import path from django.conf.urls import include from users import views as user_views

from django.contrib.auth import views as auth_views # new

urlpatterns = [ path(‘admin/‘, admin.site.urls), path(‘register/‘, user_views.register, name = ‘register’), path(‘login/‘, auth_views.LoginView.as_view(template_name=’users/login.html’), name = ‘login’), # new path(‘logout/‘, auth_views.LogoutView.as_view(template_name=’users/logout.html’), name = ‘logout’), # new path(‘’, include(‘blog.urls’)) ]

  1. as you see here, login.html and logout.html are not yet in users/templates/users/, so we need to create these 2 templates.
  2. - in users/templates/users/, create login.html:
  3. ```html
  4. {% extends "blog/base.html" %}
  5. {% load crispy_forms_tags %} # new
  6. {% block content %}
  7. <div class="content-section">
  8. <form method="POST">
  9. {% csrf_token %}
  10. <fieldset class="form-group">
  11. <legend class="border-bottom mb-4">Log In</legend>
  12. {{ form|crispy }} # new
  13. </fieldset>
  14. <div class="form-group">
  15. <button class="btn btn-outline-info" type="submit">Login</button>
  16. </div>
  17. </form>
  18. <div class="border-top pt-3">
  19. <small class="text-muted">
  20. Need An Account? <a class="ml-2" href="{% url 'register' %}">Sign Up Now</a>
  21. </small>
  22. </div>
  23. </div>
  24. {% endblock content %}
  • Recall: href=”{% url ‘[template name]’ %} is a simple method to create button as a url link.
  • Because now we actually have login.html, we setup the button from register to login
  • Also, we modify users/templates/users/register.html to set the button from login to register

  • Now we have a login page that can check incorrect username & password pair

image.png
but even if we input correct pairs, it cannot get into the right page because it’s not completed.
image.png

  • The error information said the request url is /accounts/profile/, but our format is quite different from that, so we add a line at the buttom of django_project/settings.py:

    1. # ...
    2. LOGIN_REDIRECT_URL = "blog-home" # to home.html in templates of blog/

    Then we can login successfully and be redirected into the blog homepage.

  • Change users/views.py to redirect users into the login page instead of homepage: ```python from django.shortcuts import render, redirect from django.contrib import messages from .forms import UserRegisterForm

def register(request): if request.method == ‘POST’: form = UserRegisterForm(request.POST) if form.is_valid(): form.save() username = form.cleaned_data.get(‘username’) messages.success(request, f’Your account has been created! You are now be able to login.’) # new return redirect(‘login’) else: form = UserRegisterForm()

  1. return render(request, 'users/register.html', {'form': form})
  1. <a name="Cg1sg"></a>
  2. #### Logout System:
  3. - create logout.html in users/templates/users to handle logout request:
  4. ```html
  5. {% extends "blog/base.html" %}
  6. {% block content %}
  7. <h2>You have been logged out!</h2>
  8. <div class="border-top pt-3">
  9. <small class="text-muted">
  10. <a href="{% url 'login' %}">Log In Again</a>
  11. </small>
  12. </div>
  13. {% endblock content %}
  • Now we have:

    image.png

    and logout page works.

Login/Logout/Register Navigation Buttons:

  • Now we need to make buttons in the navigation row works.

image.png

  • in blog/templates/blog/base.html:

    1. <!-- ... -->
    2. <div class="navbar-nav">
    3. {% if user.is_authenticated %}
    4. <a class="nav-item nav-link" href="{% url 'logout' %}">Logout</a>
    5. {% else %}
    6. <a class="nav-item nav-link" href="{% url 'login' %}">Login</a>
    7. <a class="nav-item nav-link" href="{% url 'register' %}">Register</a>
    8. {% endif %}
    9. </div>
    10. <!-- ... -->
  • change buttons into url links

  • if-else logic fixes:
    • if the user isn’t login, only allow login or register
    • if the user is online, only allow logout

For example, not logged in:
image.png
logged in:
image.png

Add a user profile page:

  • As usual, first we create a handler function in users/views.py:

    1. # ...
    2. def profile(request):
    3. return render(request, 'users/profile.html')
  • Then create a corresponding template, users/templates/users/profile.html:

    1. {% extends "blog/base.html" %}
    2. {% load crispy_forms_tags %}
    3. {% block content %}
    4. <h1> {{user.username}} </h1>
    5. {% endblock content %}

    {{ (variable name) }} to show variables.

  • Finally, register the path in django_project/urls.py:

    1. # ...
    2. urlpatterns = [
    3. path('admin/', admin.site.urls),
    4. path('register/', user_views.register, name = 'register'),
    5. path('profile/', user_views.profile, name = 'profile'), # new
    6. path('login/', auth_views.LoginView.as_view(template_name='users/login.html'), name = 'login'),
    7. path('logout/', auth_views.LogoutView.as_view(template_name='users/logout.html'), name = 'logout'),
    8. path('', include('blog.urls'))
    9. ]

    and we have:
    image.png

  • To see profile easily, we add a button in the navigation row in blog/templates/blog/base.html:

    1. <!-- ... -->
    2. <div class="navbar-nav">
    3. {% if user.is_authenticated %}
    4. <a class="nav-item nav-link" href="{% url 'profile' %}">Profile</a>
    5. <a class="nav-item nav-link" href="{% url 'logout' %}">Logout</a>
    6. {% else %}
    7. <a class="nav-item nav-link" href="{% url 'login' %}">Login</a>
    8. <a class="nav-item nav-link" href="{% url 'register' %}">Register</a>
    9. {% endif %}
    10. </div>
    11. <!-- ... -->

Only show profile when login:

  • Because there is no “user.username” variable if there is no user logged in, so we have to set a checking.
  • in users/views.py: ```python from django.contrib.auth.decorators import login_required # new

@login_required # new def profile(request): return render(request, ‘users/profile.html’)

  1. - If the profile request is sent and you are not logged in, redirect to the login page.
  2. - in django_project/settings.py:
  3. ```python
  4. # ...
  5. LOGIN_REDIRECT_URL = "blog-home" # to home.html in templates of blog/
  6. LOGIN_URL = 'login' # new

and to test with http://localhost:8000/profile/ and stay offline we get(take a look at the new browser url):
image.png

May. 12

User profile and picture:

  • to make our users able to add other information and pictures for their profiles.
  • in order to make it works, we need to add field for these data.
  • modify users/models.py:

Extend user profile:

  • users/models.py: ```python from django.db import models from django.contrib.auth.models import User # new

class Profile(models.Model):

  1. # create a one-to-one relationship with the imported user model
  2. # if user is deleted, also delete the profile
  3. user = models.OneToOneField(User, on_delete=models.CASCADE)
  4. # a field for images
  5. image = models.ImageField(default='default.jpg', upload_to='profile_pics')
  6. # whenever we printout profile. it will print username, either
  7. def __str__(self):
  8. return f'{self.user.username} Profile'
  1. - in the terminal, install "Pillow" package for ImageField():
  2. ```bash
  3. [root@localhost django_project]# pip install Pillow
  • then create & run migrations:
    1. [root@localhost django_project]# python manage.py makemigrations
    2. Migrations for 'users':
    3. users/migrations/0001_initial.py
    4. - Create model Profile
    5. [root@localhost django_project]# python manage.py migrate
    6. Operations to perform:
    7. Apply all migrations: admin, auth, blog, contenttypes, sessions, users
    8. Running migrations:
    9. Applying users.0001_initial... OK

Register profile:

  • users/admin.py: ```python from django.contrib import admin from .models import Profile # new

admin.site.register(Profile)

  1. to test, run server and use localhost:8000/admin/<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1589334441531-52290ba9-6f30-4b40-b846-438a60c40316.png#align=left&display=inline&height=332&margin=%5Bobject%20Object%5D&name=image.png&originHeight=664&originWidth=1022&size=59111&status=done&style=none&width=511)<br />now we have a Profile section, get inside: <br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1589334514206-65179360-57dc-4615-9268-ba5c400d7b87.png#align=left&display=inline&height=178&margin=%5Bobject%20Object%5D&name=image.png&originHeight=356&originWidth=1893&size=32194&status=done&style=none&width=946.5)<br />use "ADD PROFILE" in the top-right corner to manually add profiles:<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1589334678265-b60eba62-5b6d-4ead-886e-c81a383f6510.png#align=left&display=inline&height=213&margin=%5Bobject%20Object%5D&name=image.png&originHeight=426&originWidth=1901&size=41227&status=done&style=none&width=950.5)
  2. - try to access profile in python shell:
  3. ```bash
  4. [root@localhost django_project]# python manage.py shell
  5. Python 3.6.5 (default, Sep 10 2018, 09:39:42)
  6. Type 'copyright', 'credits' or 'license' for more information
  7. IPython 6.5.0 -- An enhanced Interactive Python. Type '?' for help.
  8. In [1]: from django.contrib.auth.models import User
  9. In [2]: user = User.objects.filter(username='root').first()
  10. In [3]: user
  11. Out[3]: <User: root>
  12. In [4]: user.profile
  13. Out[4]: <Profile: root Profile>
  14. In [5]: user.profile.image
  15. Out[5]: <ImageFieldFile: profile_pics/images.png>
  16. In [6]: user.profile.width
  17. ---------------------------------------------------------------------------
  18. AttributeError Traceback (most recent call last)
  19. <ipython-input-6-cebf93d147df> in <module>()
  20. ----> 1 user.profile.width
  21. AttributeError: 'Profile' object has no attribute 'width'
  22. In [7]: user.profile.image.width
  23. Out[7]: 225
  24. In [8]: user.profile.image.url
  25. Out[8]: 'profile_pics/images.png'
  26. In [9]: user = User.objects.filter(username='TestUser').first()
  27. In [10]: user
  28. Out[10]: <User: TestUser>
  29. In [11]: user.profile
  30. Out[11]: <Profile: TestUser Profile>
  31. In [12]: user.profile.image
  32. Out[12]: <ImageFieldFile: default.jpg>
  33. In [13]: user.profile.image.width
  34. ---------------------------------------------------------------------------
  35. FileNotFoundError Traceback (most recent call last)
  36. <ipython-input-13-9ec2104e1768> in <module>()
  37. ----> 1 user.profile.image.width
  38. /usr/local/lib/python3.6/site-packages/django/core/files/images.py in width(self)
  39. 17 @property
  40. 18 def width(self):
  41. ---> 19 return self._get_image_dimensions()[0]
  42. 20
  43. 21 @property
  44. /usr/local/lib/python3.6/site-packages/django/core/files/images.py in _get_image_dimensions(self)
  45. 26 if not hasattr(self, '_dimensions_cache'):
  46. 27 close = self.closed
  47. ---> 28 self.open()
  48. 29 self._dimensions_cache = get_image_dimensions(self, close=close)
  49. 30 return self._dimensions_cache
  50. /usr/local/lib/python3.6/site-packages/django/db/models/fields/files.py in open(self, mode)
  51. 72 self._require_file()
  52. 73 if getattr(self, '_file', None) is None:
  53. ---> 74 self.file = self.storage.open(self.name, mode)
  54. 75 else:
  55. 76 self.file.open(mode)
  56. /usr/local/lib/python3.6/site-packages/django/core/files/storage.py in open(self, name, mode)
  57. 31 def open(self, name, mode='rb'):
  58. 32 """Retrieve the specified file from storage."""
  59. ---> 33 return self._open(name, mode)
  60. 34
  61. 35 def save(self, name, content, max_length=None):
  62. /usr/local/lib/python3.6/site-packages/django/core/files/storage.py in _open(self, name, mode)
  63. 216
  64. 217 def _open(self, name, mode='rb'):
  65. --> 218 return File(open(self.path(name), mode))
  66. 219
  67. 220 def _save(self, name, content):
  68. FileNotFoundError: [Errno 2] No such file or directory: '/root/SourceCode/Python/django_project/default.jpg'
  69. In [14]: user.profile.image.url
  70. Out[14]: 'default.jpg'

(we have created 2 profiles manually:

  • root has an imported image
  • TestUser uses default image, but that image is not set yet)

Media path:

  • To make sure our uploaded images are save in the right place
  • add 2 variables for django_project/settings.py: ```python

where the uploaded files will be saved

media directory is in our base directory

MEDIA_ROOT = os.path.join(BASE_DIR, ‘media’) MEDIA_URL = ‘/media/‘

  1. to test, do another add profile with a picture:<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1589337047580-ec8f288c-e577-4c2d-8e3f-1e82e6a47462.png#align=left&display=inline&height=163&margin=%5Bobject%20Object%5D&name=image.png&originHeight=325&originWidth=472&size=19073&status=done&style=none&width=236)<br />and now we have a folder for images called "profile_pics":<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1589337100462-c109cffa-5f98-4631-aefd-954fa77609b0.png#align=left&display=inline&height=159&margin=%5Bobject%20Object%5D&name=image.png&originHeight=318&originWidth=331&size=15814&status=done&style=none&width=165.5)
  2. <a name="i4XYg"></a>
  3. #### Let profile show other information:
  4. - updates users/templates/users/profile.html:
  5. ```python
  6. {% extends "blog/base.html" %}
  7. {% load crispy_forms_tags %}
  8. {% block content %}
  9. <div class="content-section">
  10. <div class="media">
  11. <img class="rounded-circle account-img" src="{{ user.profile.image.url }}">
  12. <div class="media-body">
  13. <h2 class="account-heading">{{ user.username }}</h2>
  14. <p class="text-secondary">{{ user.email }}</p>
  15. </div>
  16. </div>
  17. <!-- FORM HERE -->
  18. </div>
  19. {% endblock content %}

May. 13

(continue)
modify django_project/urls.py:

  1. from django.contrib import admin
  2. from django.urls import path
  3. from django.conf.urls import include
  4. from users import views as user_views
  5. from django.contrib.auth import views as auth_views
  6. from django.conf import settings # new
  7. from django.conf.urls.static import static # new
  8. urlpatterns = [
  9. path('admin/', admin.site.urls),
  10. path('register/', user_views.register, name = 'register'),
  11. path('profile/', user_views.profile, name = 'profile'),
  12. path('login/', auth_views.LoginView.as_view(template_name='users/login.html'), name = 'login'),
  13. path('logout/', auth_views.LogoutView.as_view(template_name='users/logout.html'), name = 'logout'),
  14. path('', include('blog.urls'))
  15. ]
  16. # if it is in debug mode
  17. if settings.DEBUG:
  18. urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

and now in profile section, we have more info:
image.png

Upload and apply default user image:

  • Upload a “default.jpg” in media folder(same as the name in users/models.py), and it would be the default image for users who hasn’t upload images:

image.png

Create new profile right after registeration:

  • create users/signals.py for this function: ```python from django.db.models.signals import post_save from django.contrib.auth.models import User from django.dispatch import receiver from .models import Profile

@receiver(post_save, sender=User) def create_profile(sender, instance, created, **kwargs): if created: Profile.objects.create(user=instance)

@receiver(post_save, sender=User) def save_profile(sender, instance, **kwargs): instance.profile.save()

  1. - and to import signal.py when a new user is registered, we extend users/apps.py:
  2. ```python
  3. from django.apps import AppConfig
  4. class UsersConfig(AppConfig):
  5. name = 'users'
  6. def ready(self): # new
  7. import users.signals

to test, create a new user and see its profile:
image.png

May. 14

Update profile page:

  • This time we have to allow users update their profile information and image.
  • we add 2 new forms in users/forms.py for later use: ```python from django import forms from django.contrib.auth.models import User from django.contrib.auth.forms import UserCreationForm from .models import Profile # new

class UserUpdateForm(forms.ModelForm): email = forms.EmailField()

  1. class Meta:
  2. model = User
  3. fields = ['username', 'email']

class ProfileUpdateForm(forms.ModelForm): class Meta: model = Profile fields = [‘image’]

  1. - include UserUpdateForm and ProfileUpdateForm above
  2. - and extend profile in users/view.py:
  3. ```python
  4. from django.shortcuts import render, redirect
  5. from django.contrib import messages
  6. from django.contrib.auth.decorators import login_required
  7. from .forms import UserRegisterForm, UserUpdateForm, ProfileUpdateForm # new
  8. # ...
  9. @login_required
  10. def profile(request):
  11. u_form = UserUpdateForm()
  12. p_form = ProfileUpdateForm()
  13. context = {
  14. 'u_form' : u_form,
  15. 'p_form' : p_form
  16. }
  17. return render(request, 'users/profile.html', context)
  • add a form class in profile.html of templates (similar to register.html):

    1. {% extends "blog/base.html" %}
    2. {% load crispy_forms_tags %}
    3. {% block content %}
    4. <div class="content-section">
    5. <div class="media">
    6. <img class="rounded-circle account-img" src="{{ user.profile.image.url }}">
    7. <div class="media-body">
    8. <h2 class="account-heading">{{ user.username }}</h2>
    9. <p class="text-secondary">{{ user.email }}</p>
    10. </div>
    11. </div>
    12. <form method="POST" enctype="multipart/form-data">
    13. {% csrf_token %}
    14. <fieldset class="form-group">
    15. <legend class="border-bottom mb-4">Profile Info</legend>
    16. {{ u_form|crispy }}
    17. {{ p_form|crispy }}
    18. </fieldset>
    19. <div class="form-group">
    20. <button class="btn btn-outline-info" type="submit">Update</button>
    21. </div>
    22. </form>
    23. </div>
    24. {% endblock content %}

    and our profile page has imported u_from and p_form:
    image.png

    Actually update profile information:

    extend profile in users/view.py: ```python

@login_required def profile(request): if request.method == ‘POST’: # only create update forms if there is a POST request u_form = UserUpdateForm(request.POST, instance=request.user) p_form = ProfileUpdateForm(request.POST, request.FILES, instance=request.user.profile) if u_form.is_valid() and p_form.is_valid(): # check u_form.save() p_form.save() messages.success(request, f’Your account has been updated!’) return redirect(‘profile’) # redirect to the profile page

  1. else:
  2. u_form = UserUpdateForm(instance=request.user)
  3. p_form = ProfileUpdateForm(instance=request.user.profile)
  4. context = {
  5. 'u_form' : u_form,
  6. 'p_form' : p_form
  7. }
  8. return render(request, 'users/profile.html', context)
  1. and the update page actually works:<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1589513742346-cec93694-9095-46c1-8c3b-1f432092e8cb.png#align=left&display=inline&height=388&margin=%5Bobject%20Object%5D&name=image.png&originHeight=776&originWidth=1535&size=82069&status=done&style=none&width=767.5)
  2. <a name="B20zh"></a>
  3. #### Resize images:
  4. - modify users/models.py to make images are resized into 300px * 300px:
  5. ```python
  6. from django.db import models
  7. from django.contrib.auth.models import User
  8. from PIL import Image # new
  9. # ...
  10. def save(self):
  11. super().save()
  12. img = Image.open(self.image.path)
  13. if img.height > 300 or img.width > 300:
  14. output_size = (300, 300)
  15. img.thumbnail(output_size)
  16. img.save(self.image.path)

we re-upload images before, and there will be a resized image in /media (if height or width > 300px):
image.png

Put user images in posts:

  • in blog folder, modify the template of home.html:
    1. <!--...-->
    2. <article class="media content-section">
    3. <img class="rounded-circle article-img" src="{{ post.author.profile.image.url }}">
    4. <div class="media-body">
    5. <!--...-->
    after that, blog page will show images of users in the post as a small rounded-circle:
    image.png

May. 15 - 21

views.home to PostListView:

  • Define a “PostListView” to redirect ListView in /blog/view.py: ```python from django.shortcuts import render from django.views.generic import ListView # new from .models import Post

    def home(request): context = {
    1. 'posts': Post.objects.all()
    } return render(request, ‘blog/home.html’, context)

class PostListView(ListView): # new model = Post templatename = ‘blog/home.html’ # /.html context_object_name = ‘posts’ # rename from the default name into ‘posts’ ordering = [‘-date_posted’] # order from newest to oldest

  1. for now, we could think like def home() and class PostListView() do the same thing.
  2. - as above, the template name is 'blog/home.html', so we can apply PostListView in /blog/urls.py to replace views.home:
  3. ```python
  4. from django.urls import path
  5. from .views import PostListView # new
  6. from . import views
  7. urlpatterns = [
  8. # path('', views.home, name='blog-home'),
  9. path('', PostListView.as_view(), name='blog-home'), # new
  10. path('about/', views.about, name='blog-about'),
  11. ]

to test, use a browser and the url is localhost:8000
image.png
then the replacement is successful.

View for individual posts:

  • Define a “PostDetailView” in /blog/view.py:

    1. from django.shortcuts import render
    2. from django.views.generic import ListView, DetailView # new
    3. from .models import Post
    4. #...
    5. class PostDetailView(DetailView): # new
    6. model = Post
    7. #...
  • apply PostDetailView in /blog/urls.py: ```python from django.urls import path from .views import PostListView, PostDetailView # new from . import views

urlpatterns = [ path(‘’, PostListView.as_view(), name=’blog-home’), path(‘post//‘, PostDetailView.as_view(), name=’post-detail’), # new, “pk” as “primary key of the post” path(‘about/‘, views.about, name=’blog-about’), ]

  1. - Since we have no corresponding post detail template, create a post_detail.html in /templates:
  2. ```html
  3. {% extends "blog/base.html" %}
  4. {% block content %}
  5. <article class="media content-section">
  6. <img class="rounded-circle article-img" src="{{ object.author.profile.image.url }}">
  7. <div class="media-body">
  8. <div class="article-metadata">
  9. <a class="mr-2" href="#">{{ object.author }}</a>
  10. <small class="text-muted">{{ object.date_posted|date:"F d, Y" }}</small>
  11. </div>
  12. <h2 class="article-title">{{ object.title }}</a></h2>
  13. <p class="article-content">{{ object.content }}</p>
  14. </div>
  15. </article>
  16. {% endblock content %}

to test, first see localhost:8000
image.png
localhost:8000/post/1
image.png
and we have our first sent (measure in send instead of update time) blog.

Blog-home to Blog-post:

  • Add the detail of href in home.html to let titles of blogs connect to corresponding post page:

    1. <!--...-->
    2. <!--<h2><a class="article-title" href="#">{{ post.title }}</a></h2>-->
    3. <h2><a class="article-title" href="{% url 'post-detail' post.id %}">{{ post.title }}</a></h2>
    4. <!--...-->

    to test, restart the server click a blog title:
    image.png
    then we see it’s successfully redirect to the post.
    image.png

    CreateView:

  • as usual, create PostCreateView in /blog/view.py: ```python from django.shortcuts import render from django.views.generic import ( ListView, DetailView, CreateView # new ) from .models import Post

class PostCreateView(CreateView): # new model = Post fields = [‘title’, ‘content’]

  1. def form_valid(self, form): # test valid user
  2. form.instance.author = self.request.user
  3. return super().form_valid(form)
  1. - create a new path in /blog/urls.py:
  2. ```python
  3. from django.urls import path
  4. from .views import (
  5. PostListView,
  6. PostDetailView,
  7. PostCreateView # new
  8. )
  9. from . import views
  10. urlpatterns = [
  11. path('', PostListView.as_view(), name='blog-home'),
  12. path('post/<int:pk>/', PostDetailView.as_view(), name='post-detail'),
  13. path('post/new/', PostCreateView.as_view(), name='post-create'), # new
  14. path('about/', views.about, name='blog-about'),
  15. ]
  • create a new template post_form.html

    1. {% extends "blog/base.html" %}
    2. {% load crispy_forms_tags %}
    3. {% block content %}
    4. <div class="content-section">
    5. <form method="POST">
    6. {% csrf_token %}
    7. <fieldset class="form-group">
    8. <legend class="border-bottom mb-4">Blog Post</legend>
    9. {{ form|crispy }}
    10. </fieldset>
    11. <div class="form-group">
    12. <button class="btn btn-outline-info" type="submit">Post</button>
    13. </div>
    14. </form>
    15. </div>
    16. {% endblock content %}
  • finally, add a function in /blog/models.py to make sure after the new post is sent, the page can jump to the post detail:

    1. from django.db import models
    2. from django.utils import timezone
    3. from django.contrib.auth.models import User
    4. from django.urls import reverse # new
    5. # ...
    6. class Post(models.Model):
    7. # ...
    8. def get_absolute_url(self): # new
    9. return reverse('post-detail', kwargs={'pk': self.pk})

    to test, use localhost:8000/post/new to send a new post:
    image.png
    and get into the next post (url id is 5 because I just sent another blog before):
    image.png

  • Also, if the current situation is logout, add a checking step in /blog/views.py:

    1. from django.shortcuts import render
    2. from django.contrib.auth.mixins import LoginRequiredMixin # new
    3. from django.views.generic import (
    4. ListView,
    5. DetailView,
    6. CreateView
    7. )
    8. from .models import Post
    9. # ...
    10. class PostCreateView(LoginRequiredMixin, CreateView): # new
    11. model = Post
    12. fields = ['title', 'content']
    13. def form_valid(self, form):
    14. form.instance.author = self.request.user
    15. return super().form_valid(form)

    after the step you will get back to login page, if you are not logged in and access localhost:8000/post/new

Update post:

  • /blog/views.py:

    1. from django.shortcuts import render
    2. from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin # new
    3. from django.views.generic import (
    4. ListView,
    5. DetailView,
    6. CreateView,
    7. UpdateView # new
    8. )
    9. from .models import Post
    10. # ...
    11. class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView): # new
    12. model = Post
    13. fields = ['title', 'content']
    14. def form_valid(self, form): # to make sure login required
    15. form.instance.author = self.request.user
    16. return super().form_valid(form)
    17. def test_func(self): # to make sure the post is sent by the current updating user
    18. post = self.get_object()
    19. if self.request.user == post.author:
    20. return True
    21. return False
  • apply PostUpdateView in /blog/urls.py: ```python from django.urls import path from .views import ( PostListView, PostDetailView, PostCreateView, PostUpdateView # new ) from . import views

urlpatterns = [ path(‘’, PostListView.as_view(), name=’blog-home’), path(‘post//‘, PostDetailView.as_view(), name=’post-detail’), path(‘post/new/‘, PostCreateView.as_view(), name=’post-create’), path(‘post//update’, PostUpdateView.as_view(), name=’post-update’), # new path(‘about/‘, views.about, name=’blog-about’), ]

  1. to test, update by localhost:8000/post/5/update<br />(actually send a new post or update both apply the template "post_form.html")<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1590103415571-934e5346-821e-4ab4-b86d-24a502c232e2.png#align=left&display=inline&height=367&margin=%5Bobject%20Object%5D&name=image.png&originHeight=734&originWidth=1610&size=60824&status=done&style=none&width=805)<br />successful:<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1590103436267-1b006e9c-b9fb-43e4-b2f0-340a24faf6a9.png#align=left&display=inline&height=304&margin=%5Bobject%20Object%5D&name=image.png&originHeight=607&originWidth=1582&size=51506&status=done&style=none&width=791)<br />and if access/post/3/update (Post 3 is not sent by curren logged in account)<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1590103633227-e6ad15e8-f7b0-4fff-a40e-f85730d3d2f2.png#align=left&display=inline&height=110&margin=%5Bobject%20Object%5D&name=image.png&originHeight=219&originWidth=466&size=9608&status=done&style=none&width=233)
  2. <a name="H0p5K"></a>
  3. #### Delete post:
  4. - again, use the pattern before
  5. - /blog/views.py for a new view:
  6. ```python
  7. from django.shortcuts import render
  8. from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
  9. from django.views.generic import (
  10. ListView,
  11. DetailView,
  12. CreateView,
  13. UpdateView,
  14. DeleteView # new
  15. )
  16. from .models import Post
  17. # ...
  18. class PostDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView): # new
  19. model = Post
  20. success_url = '/' # if delete success, get back to blog-home
  21. def test_func(self):
  22. post = self.get_object()
  23. if self.request.user == post.author:
  24. return True
  25. return False
  • apply PostDeleteView in /blog/urls.py ```python from django.urls import path from .views import ( PostListView, PostDetailView, PostCreateView, PostUpdateView, PostDeleteView # new ) from . import views

urlpatterns = [ path(‘’, PostListView.as_view(), name=’blog-home’), path(‘post//‘, PostDetailView.as_view(), name=’post-detail’), path(‘post/new/‘, PostCreateView.as_view(), name=’post-create’), path(‘post//update’, PostUpdateView.as_view(), name=’post-update’), path(‘post//delete/‘, PostDeleteView.as_view(), name=’post-delete’), # new path(‘about/‘, views.about, name=’blog-about’), ]

  1. - a template called "post_confirm_delete.html" for a separate delete page:
  2. - Here we have a headline to notice user to make sure they want to delete, and a cancel button to get back to the post detail page:
  3. ```html
  4. {% extends "blog/base.html" %}
  5. {% block content %}
  6. <div class="content-section">
  7. <form method="POST">
  8. {% csrf_token %}
  9. <fieldset class="form-group">
  10. <legend class="border-bottom mb-4">Delete Post</legend>
  11. <h2>Are you sure you want to delete the post "{{ object.title }}"</h2>
  12. </fieldset>
  13. <div class="form-group">
  14. <button class="btn btn-outline-danger" type="submit">Yes, Delete</button> <!--"btn btn-outline-danger" can create a warning red button-->
  15. <a class="btn btn-outline-secondary" href="{% url 'post-detail' object.id %}">Cancel</a>
  16. </div>
  17. </form>
  18. </div>
  19. {% endblock content %}

to test, I want to delete this accidentally created post 4:
image.png
use “Yes, Delete” in localhost:8000/post/4/delete/
image.png
and the delete is success, get back to the blog-home page:
image.png

May. 22

Buttons for create/update/delete views:

  • base.html in templates:

    1. <!--...-->
    2. {% if user.is_authenticated %}
    3. <a class="nav-item nav-link" href="{% url 'post-create' %}">New Post</a> <!--new-->
    4. <a class="nav-item nav-link" href="{% url 'profile' %}">Profile</a>
    5. <a class="nav-item nav-link" href="{% url 'logout' %}">Logout</a>
    6. {% else %}
    7. <!--...-->
  • post_detail.html in templates:

    1. <!--...-->
    2. <small class="text-muted">{{ object.date_posted|date:"F d, Y" }}</small>
    3. {% if object.author == user %}
    4. <div>
    5. <a class="btn btn-secondary btn-sm mt-1 mb-1" href="{% url 'post-update' object.id %}">Update</a>
    6. <a class="btn btn-danger btn-sm mt-1 mb-1" href="{% url 'post-delete' object.id %}">Delete</a>
    7. </div>
    8. {% endif %}
    9. <!--...-->

    now we have corresponding buttons for 3 methods:
    image.png
    image.png

    About Pagination:

  • “Page->Paginate->Pagination” means split texts into multiple pages.

  • We need pagination if there are too many contents.

  • First we need enough data to simulate that situation, and here we download a “posts.json” in github: https://github.com/CoreyMSchafer/code_snippets/blob/master/Django_Blog/11-Pagination/django_project/posts.json

  • import the content:
    1. [root@localhost django_project]# python manage.py shell
    2. >>> import json
    3. >>> from blog.models import Post
    4. >>> with open('posts.json') as f:
    5. ... posts_json = json.load(f)
    6. ...
    7. >>> for post in posts_json:
    8. ... post = Post(title=post['title'], content=post['content'], author_id=post['user_id'])
    9. ... post.save()
    10. ...
    11. >>> exit()
    then runserver to see blog-home page.
    image.png
    and it’s successful.

so here we have a example to apply paginator:

  1. [root@localhost django_project]# python manage.py shell
  2. >>> from django.core.paginator import Paginator
  3. >>> posts = ['1', '2', '3', '4', '5'] # We have 5 posts
  4. >>> p = Paginator(posts, 2) # paginate posts into 2 posts 1 page
  5. >>> p.num_pages # int(5/2) = 3
  6. 3
  7. >>> for page in p.page_range:
  8. ... print(page)
  9. ...
  10. 1
  11. 2
  12. 3
  13. >>> p1 = p.page(1)
  14. >>> p1
  15. <Page 1 of 3>
  16. >>> p1.number
  17. 1
  18. >>> p1.object_list # list of objects on this page
  19. ['1', '2']
  20. >>> p1.has_previous() # has the previous page
  21. False
  22. >>> p1.has_next() # has the next page
  23. True
  24. >>> p1.next_page_number()
  25. 2
  26. >>> exit()

Apply Pagination:

  • it’s simple to apply paginate in /blog/views.py:

    1. # ...
    2. class PostListView(ListView):
    3. model = Post
    4. template_name = 'blog/home.html'
    5. context_object_name = 'posts'
    6. ordering = ['-date_posted']
    7. paginate_by = 2 # new
    8. # ...

    and we can see blog home page only has 2 blogs now:
    image.png
    but because we have no corresponding buttons to the previous or to the next page, we only have do that by add page number(add /?page=[page number]):
    image.png
    also, we would receive a 404 not found is the page number does not exist.


    Change the page number:

  • a bit compliated if else relationship

  • add those lines in home.html in templates: ```html {% if is_paginated %}

    {% if page_obj.has_previous %}

    1. <a class="btn btn-outline-info mb-4" href="?page=1">First</a>
    2. <a class="btn btn-outline-info mb-4" href="?page={{ page_obj.previous_page_number }}">Previous</a>

    {% endif %}

    {% for num in page_obj.paginator.page_range %}

    1. {% if page_obj.number == num %}
    2. <a class="btn btn-info mb-4" href="?page={{ num }}">{{ num }}</a>
    3. {% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
    4. <a class="btn btn-outline-info mb-4" href="?page={{ num }}">{{ num }}</a>
    5. {% endif %}

    {% endfor %}

    {% if page_obj.has_next %}

    1. <a class="btn btn-outline-info mb-4" href="?page={{ page_obj.next_page_number }}">Next</a>
    2. <a class="btn btn-outline-info mb-4" href="?page={{ page_obj.paginator.num_pages }}">Last</a>

    {% endif %}

{% endif %}

  1. to test, runserver (now paginate_by = 5 instead of 2):<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1590191493993-122c832d-62c9-484f-a212-06ea6c0b7d01.png#align=left&display=inline&height=411&margin=%5Bobject%20Object%5D&name=image.png&originHeight=821&originWidth=1223&size=102071&status=done&style=none&width=611.5)
  2. <a name="DCvCM"></a>
  3. ### May. 23
  4. <a name="UL3mm"></a>
  5. #### User post page:
  6. - we want to get a function to show posts from a filtered user only.
  7. - update /blog/views.py for a UserPostListView:
  8. ```python
  9. from django.shortcuts import render, get_object_or_404 # new
  10. from django.contrib.auth.models import User # new
  11. from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
  12. from django.views.generic import (
  13. ListView,
  14. DetailView,
  15. CreateView,
  16. UpdateView,
  17. DeleteView
  18. )
  19. from .models import Post
  20. # ...
  21. class UserPostListView(ListView):
  22. model = Post
  23. template_name = 'blog/user_posts.html'
  24. context_object_name = 'posts'
  25. paginate_by = 5
  26. def get_queryset(self): # to avoid if cannot find the user
  27. user = get_object_or_404(User, username=self.kwargs.get('username'))
  28. return Post.objects.filter(author=user).order_by('-date_posted') # order from newest to oldest
  29. # ...
  • in /blog/urls.py import that view: ```python from django.urls import path from .views import ( PostListView, PostDetailView, PostCreateView, PostUpdateView, PostDeleteView, UserPostListView # new ) from . import views

urlpatterns = [ path(‘’, PostListView.as_view(), name=’blog-home’), path(‘user/‘, UserPostListView.as_view(), name=’user-posts’), # new path(‘post//‘, PostDetailView.as_view(), name=’post-detail’), path(‘post/new/‘, PostCreateView.as_view(), name=’post-create’), path(‘post//update’, PostUpdateView.as_view(), name=’post-update’), path(‘post//delete/‘, PostDeleteView.as_view(), name=’post-delete’), path(‘about/‘, views.about, name=’blog-about’), ]

  1. - a new template user_posts.html (based on home.html):
  2. ```html
  3. {% extends "blog/base.html" %}
  4. {% block content %}
  5. <h1 class="mb-3">Posts by {{ view.kwargs.username }} ({{ page_obj.paginator.count }})</h1>
  6. {% for post in posts %}
  7. <article class="media content-section">
  8. <img class="rounded-circle article-img" src="{{ post.author.profile.image.url }}">
  9. <div class="media-body">
  10. <div class="article-metadata">
  11. <a class="mr-2" href="{% url 'user-posts' post.author.username %}">{{ post.author }}</a>
  12. <small class="text-muted">{{ post.date_posted|date:"F d, Y" }}</small>
  13. </div>
  14. <h2><a class="article-title" href="{% url 'post-detail' post.id %}">{{ post.title }}</a></h2>
  15. <p class="article-content">{{ post.content }}</p>
  16. </div>
  17. </article>
  18. {% endfor %}
  19. {% if is_paginated %}
  20. {% if page_obj.has_previous %}
  21. <a class="btn btn-outline-info mb-4" href="?page=1">First</a>
  22. <a class="btn btn-outline-info mb-4" href="?page={{ page_obj.previous_page_number }}">Previous</a>
  23. {% endif %}
  24. {% for num in page_obj.paginator.page_range %}
  25. {% if page_obj.number == num %}
  26. <a class="btn btn-info mb-4" href="?page={{ num }}">{{ num }}</a>
  27. {% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
  28. <a class="btn btn-outline-info mb-4" href="?page={{ num }}">{{ num }}</a>
  29. {% endif %}
  30. {% endfor %}
  31. {% if page_obj.has_next %}
  32. <a class="btn btn-outline-info mb-4" href="?page={{ page_obj.next_page_number }}">Next</a>
  33. <a class="btn btn-outline-info mb-4" href="?page={{ page_obj.paginator.num_pages }}">Last</a>
  34. {% endif %}
  35. {% endif %}
  36. {% endblock content %}
  • in post_deatils.html template, add the author href before:
    1. <a class="mr-2" href="{% url 'user-posts' object.author.username %}">{{ object.author }}</a>
    2. <!--<a class="mr-2" href="#">{{ object.author }}</a>-->
    to test, runserver and use url http://localhost:8000/user/[username]
    image.png
    and the page is filtered by the user, while only posts sent by that user are shown.

also, if we click the username in a post:
image.png
…we get into that user’s post page:
image.png

May. 25

Password reset:

  • to simulate sometimes a user have to reset the password
  • First, we need a new pattern for this function in /django_project/urls.py:

    1. # ...
    2. urlpatterns = [
    3. path('admin/', admin.site.urls),
    4. path('register/', user_views.register, name = 'register'),
    5. path('profile/', user_views.profile, name = 'profile'),
    6. path('login/', auth_views.LoginView.as_view(template_name='users/login.html'), name = 'login'),
    7. path('logout/', auth_views.LogoutView.as_view(template_name='users/logout.html'), name = 'logout'),
    8. path('password-reset/',
    9. auth_views.PasswordResetView.as_view(template_name='users/password_reset.html'),
    10. name = 'password_reset'), # new
    11. path('', include('blog.urls'))
    12. ]
    13. # ...
  • a corresponding template called “password_reset.html” in /users:

    1. {% extends "blog/base.html" %}
    2. {% load crispy_forms_tags %}
    3. {% block content %}
    4. <div class="content-section">
    5. <form method="POST">
    6. {% csrf_token %}
    7. <fieldset class="form-group">
    8. <legend class="border-bottom mb-4">Reset Password</legend>
    9. {{ form|crispy }}
    10. </fieldset>
    11. <div class="form-group">
    12. <button class="btn btn-outline-info" type="submit">Request Password Reset</button>
    13. </div>
    14. </form>
    15. </div>
    16. {% endblock content %}

    this page is bascially changed from login.html in users templates.

  • so it’s not enough to reset the password, we need more detailed templates.

  • a new pattern for “send email to the address” in /django_project/urls.py:

    1. # ...
    2. urlpatterns = [
    3. path('admin/', admin.site.urls),
    4. path('register/', user_views.register, name = 'register'),
    5. path('profile/', user_views.profile, name = 'profile'),
    6. path('login/', auth_views.LoginView.as_view(template_name='users/login.html'), name = 'login'),
    7. path('logout/', auth_views.LogoutView.as_view(template_name='users/logout.html'), name = 'logout'),
    8. path('password-reset/',
    9. auth_views.PasswordResetView.as_view(
    10. template_name='users/password_reset.html'
    11. ),
    12. name = 'password_reset'),
    13. path('password-reset/done/',
    14. auth_views.PasswordResetDoneView.as_view(
    15. template_name='users/password_reset_done.html'
    16. ),
    17. name = 'password_reset_done'), # new
    18. path('', include('blog.urls'))
    19. ]
    20. # ...
  • a corresponding template “password_reset_done.html” in /users:

    1. {% extends "blog/base.html" %}
    2. {% block content %}
    3. <div class="alert alert-info">
    4. An email has been sent with instructions to reset your password
    5. </div>
    6. {% endblock content %}
  • then we require a confirmation page for password reset,

  • a new pattern in /django_project/urls.py:

    1. # ...
    2. urlpatterns = [
    3. path('admin/', admin.site.urls),
    4. path('register/', user_views.register, name = 'register'),
    5. path('profile/', user_views.profile, name = 'profile'),
    6. path('login/', auth_views.LoginView.as_view(template_name='users/login.html'), name = 'login'),
    7. path('logout/', auth_views.LogoutView.as_view(template_name='users/logout.html'), name = 'logout'),
    8. path('password-reset/',
    9. auth_views.PasswordResetView.as_view(
    10. template_name='users/password_reset.html'
    11. ),
    12. name = 'password_reset'),
    13. path('password-reset/done/',
    14. auth_views.PasswordResetDoneView.as_view(
    15. template_name='users/password_reset_done.html'
    16. ),
    17. name = 'password_reset_done'),
    18. path('password-reset-confirm/<uidb64>/<token>/',
    19. auth_views.PasswordResetConfirmView.as_view(
    20. template_name='users/password_reset_confirm.html'
    21. ),
    22. name='password_reset_confirm'), # new
    23. path('', include('blog.urls'))
    24. ]
    25. # ...
  • a corresponding template “password_reset_confirm.html” in /users:

    1. {% extends "blog/base.html" %}
    2. {% load crispy_forms_tags %}
    3. {% block content %}
    4. <div class="content-section">
    5. <form method="POST">
    6. {% csrf_token %}
    7. <fieldset class="form-group">
    8. <legend class="border-bottom mb-4">Reset Password</legend>
    9. {{ form|crispy }}
    10. </fieldset>
    11. <div class="form-group">
    12. <button class="btn btn-outline-info" type="submit">Reset Password</button>
    13. </div>
    14. </form>
    15. </div>
    16. {% endblock content %}
  • and we need to tell the user the password reset is done.

  • in /django_project/urls.py:

    1. urlpatterns = [
    2. path('admin/', admin.site.urls),
    3. path('register/', user_views.register, name = 'register'),
    4. path('profile/', user_views.profile, name = 'profile'),
    5. path('login/', auth_views.LoginView.as_view(template_name='users/login.html'), name = 'login'),
    6. path('logout/', auth_views.LogoutView.as_view(template_name='users/logout.html'), name = 'logout'),
    7. path('password-reset/',
    8. auth_views.PasswordResetView.as_view(
    9. template_name='users/password_reset.html'
    10. ),
    11. name = 'password_reset'),
    12. path('password-reset/done/',
    13. auth_views.PasswordResetDoneView.as_view(
    14. template_name='users/password_reset_done.html'
    15. ),
    16. name = 'password_reset_done'),
    17. path('password-reset-confirm/<uidb64>/<token>/',
    18. auth_views.PasswordResetConfirmView.as_view(
    19. template_name='users/password_reset_confirm.html'
    20. ),
    21. name='password_reset_confirm'),
    22. path('password-reset-complete/',
    23. auth_views.PasswordResetCompleteView.as_view(
    24. template_name='users/password_reset_complete.html'
    25. ),
    26. name='password_reset_complete'), # new
    27. path('', include('blog.urls'))
    28. ]
  • and a new template “password_reset_complete.html” in /users:

    1. {% extends "blog/base.html" %}
    2. {% block content %}
    3. <div class="alert alert-info">
    4. Your password has been set.
    5. </div>
    6. <a href="{% url 'login' %}">Sign In Here</a>
    7. {% endblock content %}
  • finally, we need a “forgot password?” in users/login.html:

    1. <!--...-->
    2. <div class="form-group">
    3. <button class="btn btn-outline-info" type="submit">Login</button>
    4. <small class="text-muted ml-2">
    5. <a href="{% url 'password_reset' %}">Forgot Password?</a>
    6. </small>
    7. </div>
    8. <!--...-->

    save() method adjust:

  • we would apply some default values on save() in /users/models.py:

    1. # ...
    2. # def save(self):
    3. def save(self, force_insert=False, force_update=False, using=None):
    4. super().save()
    5. # ...

    Send reset mails:

  • set sender side in /django_project/settings.py:

    1. # ...
    2. EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
    3. EMAIL_HOST = 'smtp.gmail.com'
    4. EMAIL_PORT = 587
    5. EMAIL_USE_TLS = True
    6. EMAIL_HOST_USER = os.environ.get('EMAIL_USER')
    7. EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_PASS')

    Caution**: I am doing this project in a virtual machine, so that “os.environ.get(‘EMAIL_USER’)” and “os.environ.get(‘EMAIL_PASS’)” would get nothing. As a result, I changed EMAIL_HOST_USER and EMAIL_HOST_PASSWORD to a useable gmail account and password, while I changed my setting of google account to allow access from less secure apps**, but I suggest you turn that off after the test.

To test, create an account with that “allowed less secure access” email address above.
use “Forgot Password? button” or localhost:8000/password-reset/ to send that request:
image.png

in the mailbox we got an confirmation mail:
image.png
use the link to complete that reset:
image.png

May. 26

modify /users/models.py:

  • change /users/models.py again to allow any arbitrary number of positional or keyword arguments:

    1. # ...
    2. # def save(self, force_insert=False, force_update=False, using=None):
    3. def save(self, *args, **kawrgs):
    4. #super().save()
    5. super().save(*args, **kawrgs)
    6. # ...

    Deploy to a Linux Server(server deployment):

  • For the program right now, we have a basically complete application that is eligible to be deployed online instead of just use a virtual machine.

  • To accomplish that, this time we apply “linode”, first we create an account and we see this UI:

image.png
“Linodes” to create a new server:
image.png
copy SSH Access in networking and use a terminal to access the server.

  • after ssh, update & upgrade (I use ubuntu):

    1. root@localhost:~# apt-get update && apt-get upgrade
  • reset hostname:

    1. root@localhost:~# hostnamectl set-hostname django-server
    2. root@localhost:~# hostname
    3. django-server
  • use “nano” to add the ssh path before into /etc/hosts:

    1. root@localhost:~# nano /etc/hosts

    image.png

  • add a new user “coreyms”, and treat it as sudo: ``bash root@localhost:~# adduser coreyms Adding usercoreyms’ … Adding new group coreyms' (1000) ... Adding new usercoreyms’ (1000) with group coreyms' ... Creating home directory/home/coreyms’ … Copying files from `/etc/skel’ … New password: Retype new password: passwd: password updated successfully Changing the user information for coreyms Enter the new value, or press ENTER for the default Full Name []: Corey Schafer Room Number []: Work Phone []: Home Phone []: Other []: Is the information correct? [Y/n] Y

root@localhost:~# adduser coreyms sudo Adding user coreyms' to groupsudo’ … Adding user coreyms to group sudo Done.

  1. and we can just log into coreyms next with ssh next time:
  2. ```bash
  3. [root@localhost django_project]# ssh coreyms@172.105.102.73
  4. coreyms@172.105.102.73's password:
  • make a ~/.ssh root directory:

    1. coreyms@django-server:~$ pwd
    2. /home/coreyms
    3. coreyms@django-server:~$ mkdir -p ~/.ssh
    4. coreyms@django-server:~$ ls -la
    5. total 32
    6. drwxr-xr-x 5 coreyms coreyms 4096 May 28 15:42 .
    7. drwxr-xr-x 3 root root 4096 May 28 14:34 ..
    8. -rw-r--r-- 1 coreyms coreyms 220 May 28 14:34 .bash_logout
    9. -rw-r--r-- 1 coreyms coreyms 3771 May 28 14:34 .bashrc
    10. drwx------ 2 coreyms coreyms 4096 May 28 14:52 .cache
    11. drwx------ 3 coreyms coreyms 4096 May 28 14:52 .gnupg
    12. -rw-r--r-- 1 coreyms coreyms 807 May 28 14:34 .profile
    13. drwxrwxr-x 2 coreyms coreyms 4096 May 28 15:42 .ssh
  • open another terminal to generate rsa key:

    1. [root@localhost ~]# ssh-keygen -b 4096
    2. Generating public/private rsa key pair.
    3. Enter file in which to save the key (/root/.ssh/id_rsa):
    4. Enter passphrase (empty for no passphrase):
    5. Enter same passphrase again:
    6. Your identification has been saved in /root/.ssh/id_rsa.
    7. Your public key has been saved in /root/.ssh/id_rsa.pub.
  • access coreyms by ssh and securely copy the key

  • then change file modes of ssh:
    ```bash [root@localhost ~]# scp /root/.ssh/id_rsa.pub coreyms@172.105.102.73:~/.ssh/authorized_keys coreyms@172.105.102.73’s password: id_rsa.pub 100% 752 0.7KB/s 00:00

another terminal

coreyms@django-server:~$ ls .ssh authorized_keys coreyms@django-server:~$ sudo chmod 700 ~/.ssh/ [sudo] password for coreyms: coreyms@django-server:~$ sudo chmod 600 ~/.ssh/*

  1. - modify the config of sshd, and restart sshd:
  2. ```bash
  3. coreyms@django-server:~$ sudo nano /etc/ssh/sshd_config
  4. # PermitRootLogin yes -> no
  5. # PasswordAuthentication yes -> no
  6. coreyms@django-server:~$ sudo systemctl restart sshd
  • install ufw, and change ufw settings
  • (ufw - program for managing a netfilter firewall) ```bash coreyms@django-server:~$ sudo apt-get install ufw coreyms@django-server:~$ sudo ufw default allow outgoing Default outgoing policy changed to ‘allow’ (be sure to update your rules accordingly) coreyms@django-server:~$ sudo ufw default deny incoming Default incoming policy changed to ‘deny’ (be sure to update your rules accordingly) coreyms@django-server:~$ sudo ufw allow ssh Rules updated Rules updated (v6) coreyms@django-server:~$ sudo ufw allow 8000 Rules updated Rules updated (v6) coreyms@django-server:~$ sudo ufw enable Command may disrupt existing ssh connections. Proceed with operation (y|n)? y Firewall is active and enabled on system startup coreyms@django-server:~$ sudo ufw status Status: active

To Action From


22/tcp ALLOW Anywhere
8000 ALLOW Anywhere
22/tcp (v6) ALLOW Anywhere (v6)
8000 (v6) ALLOW Anywhere (v6)

  1. <a name="xxOHQ"></a>
  2. ### May. 27
  3. (continue)
  4. - at first, create a virtual environment to pick the requirement to run our project
  5. - because I meet problem to use the virtual environment, so I might have too many lines for requirements.txt
  6. ```bash
  7. [root@localhost django_project]# pip freeze
  8. appdirs==1.4.4
  9. asgiref==3.2.7
  10. attrs==19.3.0
  11. awsebcli==3.18.1
  12. bcrypt==3.1.7
  13. blessed==1.17.5
  14. botocore==1.15.49
  15. cached-property==1.5.1
  16. cement==2.8.2
  17. certifi==2020.4.5.1
  18. cffi==1.14.0
  19. chardet==3.0.4
  20. colorama==0.4.3
  21. cryptography==2.9.2
  22. distlib==0.3.0
  23. Django==2.1.15
  24. django-crispy-forms==1.9.1
  25. docker==4.2.0
  26. docker-compose==1.25.5
  27. dockerpty==0.4.1
  28. docopt==0.6.2
  29. docutils==0.15.2
  30. filelock==3.0.12
  31. future==0.16.0
  32. idna==2.7
  33. importlib-metadata==1.6.0
  34. jmespath==0.10.0
  35. jsonschema==3.2.0
  36. paramiko==2.7.1
  37. pathspec==0.5.9
  38. Pillow==7.1.2
  39. pycparser==2.20
  40. PyNaCl==1.3.0
  41. pyrsistent==0.16.0
  42. python-dateutil==2.8.0
  43. pytz==2020.1
  44. PyYAML==5.3.1
  45. requests==2.20.1
  46. semantic-version==2.5.0
  47. six==1.11.0
  48. sqlparse==0.3.1
  49. termcolor==1.1.0
  50. texttable==1.6.2
  51. urllib3==1.24.3
  52. virtualenv==20.0.21
  53. wcwidth==0.1.9
  54. websocket-client==0.57.0
  55. zipp==3.1.0
  56. [root@localhost django_project]# pip freeze > requirements.txt
  • I manually left those lines:
    1. certifi==2020.4.5.1
    2. chardet==3.0.4
    3. Django==2.1.15
    4. django-crispy-forms==1.9.1
    5. idna==2.7
    6. Pillow==7.1.2
    7. pytz==2020.1
    8. requests==2.20.1
    9. urllib3==1.24.3
  • copy the source code of our project to the linux server:

    1. [root@localhost Python]# scp -r django_project coreyms@172.105.102.73:~/

    image.png

  • for the linux server, install some packages

  • and create “venv” into django_project/
  • change the virtual environment into “venv”
  • install required packages ```bash coreyms@django-server:~$ sudo apt-get install python3-pip coreyms@django-server:~$ sudo apt-get install python3-venv

coreyms@django-server:~$ python3 -m venv django_project/venv coreyms@django-server:~$ ls django_project/ blog django_project media profile_pics venv db.sqlite3 manage.py posts.json users

coreyms@django-server:~$ cd django_project/ coreyms@django-server:~/django_project$ source venv/bin/activate

(venv) coreyms@django-server:~/django_project$ pip install -r requirements.txt

  1. - modify setting.py:
  2. ```bash
  3. (venv) coreyms@django-server:~/django_project$ sudo nano django_project/settings.py
  4. # ALLOWED_HOSTS = [] -> ['(ssh ip before)']
  5. # add STATIC_ROOT = os.path.join(BASE_DIR, 'static')
  6. (venv) coreyms@django-server:~/django_project$ python manage.py collectstatic
  7. 120 static files copied to '/home/coreyms/django_project/static'.
  • now we can runserver: ```bash (venv) coreyms@django-server:~/django_project$ python manage.py runserver 0.0.0.0:8000 Performing system checks…

System check identified no issues (0 silenced). May 27, 2020 - 20:46:20 Django version 2.1.15, using settings ‘django_project.settings’ Starting development server at http://0.0.0.0:8000/ Quit the server with CONTROL-C.

  1. 0.0.0.0 stands for ssh ip, so we access with my own ssh ip path: [http://172.105.102.73:8000/](http://172.105.102.73:8000/)<br />The deployment is successful, and I am able to use all functions. <br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1590698895107-6854eaed-f5aa-4145-9f10-a8b9e08b9c97.png#align=left&display=inline&height=401&margin=%5Bobject%20Object%5D&name=image.png&originHeight=802&originWidth=1554&size=111073&status=done&style=none&width=777)<br />new created user: <br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1590699026061-9bd90756-8d3c-4c1a-a746-0464fa583da0.png#align=left&display=inline&height=365&margin=%5Bobject%20Object%5D&name=image.png&originHeight=729&originWidth=1365&size=78523&status=done&style=none&width=682.5)
  2. <a name="OGhaB"></a>
  3. #### About reset-password:
  4. it's related to some more detailed use of new packages, so I halt this part for later. <br />[https://www.youtube.com/watch?v=Sa_kQheCnds&list=PL-osiE80TeTtoQCKZ03TU5fNfx2UY6U4p&index=13](https://www.youtube.com/watch?v=Sa_kQheCnds&list=PL-osiE80TeTtoQCKZ03TU5fNfx2UY6U4p&index=13)
  5. <a name="qzfEQ"></a>
  6. ### May. 28
  7. <a name="Be1By"></a>
  8. #### Using AWS S3 for File Uploads:
  9. - For this part, we need an Amazon Web Services (AWS) account to apply AWS S3.
  10. - S3 stands for "Simple Storage Service", it's web service that users can upload and save files into servers of AWS.
  11. - First, setup an AWS account.
  12. - Then get into the console page, input "S3" for the service we want:
  13. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1590793514311-2763ccd3-f85c-44d8-9f9c-6541714bde2f.png#align=left&display=inline&height=407&margin=%5Bobject%20Object%5D&name=image.png&originHeight=814&originWidth=1547&size=120316&status=done&style=none&width=773.5)<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1590794499164-266bbed4-fd8e-415d-821a-8833097eb4e5.png#align=left&display=inline&height=354&margin=%5Bobject%20Object%5D&name=image.png&originHeight=708&originWidth=1865&size=100622&status=done&style=none&width=932.5)
  14. - create a new bucket:
  15. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1590794458871-344ef729-28c6-4830-a706-44144fd3c821.png#align=left&display=inline&height=359&margin=%5Bobject%20Object%5D&name=image.png&originHeight=717&originWidth=1101&size=50309&status=done&style=none&width=550.5)
  16. - get into the bucket just created, we select "Permissions" -> "CORS configuration"
  17. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/1243266/1590794677014-7b534be1-a05b-40a1-9241-cf9a427bbfef.png#align=left&display=inline&height=356&margin=%5Bobject%20Object%5D&name=image.png&originHeight=711&originWidth=1421&size=58083&status=done&style=none&width=710.5)<br />copy this cors config text inside, and save it:
  18. ```html
  19. <?xml version="1.0" encoding="UTF-8"?>
  20. <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
  21. <CORSRule>
  22. <AllowedOrigin>*</AllowedOrigin>
  23. <AllowedMethod>GET</AllowedMethod>
  24. <AllowedMethod>POST</AllowedMethod>
  25. <AllowedMethod>PUT</AllowedMethod>
  26. <AllowedHeader>*</AllowedHeader>
  27. </CORSRule>
  28. </CORSConfiguration>
  • back to the console, switch to “IAM”:

image.png
add a new user:
image.png
grant S3 Full Access Policy:
image.png
after the new user is created, we are given a pair of access key id and secret access key:
image.png

  • edit ~/.bash_profile, add these lines:

    1. export AWS_ACCESS_KEY_ID="..." #string of the Access Key ID
    2. export AWS_SECRET_ACCESS_KEY="..." #string of the Secret Access Key
    3. export AWS_STORAGE_BUCKET_NAME="django-blogs-files"
  • install some packages:

    1. (django_env) [root@localhost Python]# pip install boto3
    2. (django_env) [root@localhost django_project]# pip install django-storages
  • update /django_project/settings.py: ```bash

INSTALLED_APPS = [ ‘blog.apps.BlogConfig’, ‘users.apps.UsersConfig’, ‘crispy_forms’, ‘django.contrib.admin’, ‘django.contrib.auth’, ‘django.contrib.contenttypes’, ‘django.contrib.sessions’, ‘django.contrib.messages’, ‘django.contrib.staticfiles’, ‘storages’, # new ]

AWS_ACCESS_KEY_ID = os.environ.get(‘AWS_ACCESS_KEY_ID’) AWS_SECRET_ACCESS_KEY = os.environ.get(‘AWS_SECRET_ACCESS_KEY’) AWS_STORAGE_BUCKET_NAME = os.environ.get(‘AWS_STORAGE_BUCKET_NAME’)

AWS_S3_FILE_OVERWRITE = False AWS_DEFAULT_ACL = None

DEFAULT_FILE_STORAGE = ‘storages.backends.s3boto3.S3Boto3Storage’

AWS_S3_REGION_NAME = ‘us-east-2’ # my AWS region AWS_S3_SIGNATURE_VERSION = ‘s3v4

  1. - and because save method changes, don't need to define save method on our own, so change /users/models.py:
  2. ```python
  3. class Profile(models.Model):
  4. user = models.OneToOneField(User, on_delete=models.CASCADE)
  5. image = models.ImageField(default='default.jpg', upload_to='profile_pics')
  6. def __str__(self):
  7. return f'{self.user.username} Profile'
  8. # def save(self, *args, **kawrgs):
  9. # super().save(*args, **kawrgs)
  10. # img = Image.open(self.image.path)
  11. # if img.height > 300 or img.width > 300:
  12. # output_size = (300, 300)
  13. # img.thumbnail(output_size)
  14. # img.save(self.image.path)
  • now get back to S3 page of AWS and enter the bucket at first:

image.png

  • upload stuff in /media to overview page:

image.png
and allow public access:
image.png
to test, runserver:

  1. [root@localhost django_project]# source ~/django_env/bin/activate
  2. (django_env) [root@localhost django_project]# python manage.py runserver

this S3 modification is successful, we can pick images from the bucket before:
image.png

  • here we update an image for a user:

image.png
and then we can see the corresponding image is in the corresponding folder of the bucket:
image.png