Get started with Django Part 1

For this part of the tutorial, we’ll create an app called

hello_world

, which you’ll subsequently delete as its not necessary for our personal portfolio site.

*Note - I'll send stuff from BundeledNotes/Scrittor in here later. Things mainly regarding terminals/prompts - using Git Bash on HyperJS will be really useful for this.

Another idea: BundledNotes premium (for webapp)

Git bash, clone gizmotronn/django-start repo into a folder on all devices, then use terminal (hyperJS) and git bash.

HyperJS setup:

// Future versions of Hyper may add additional config options,
// which will not automatically be merged into this file.
// See https://hyper.is#cfg for all currently supported options.

module.exports = {
  config: {
    // choose either `'stable'` for receiving highly polished,
    // or `'canary'` for less polished but more frequent updates
    updateChannel: 'stable',

    // default font size in pixels for all tabs
    fontSize: 12,

    // font family with optional fallbacks
    fontFamily: 'Menlo, "DejaVu Sans Mono", Consolas, "Lucida Console", monospace',

    // default font weight: 'normal' or 'bold'
    fontWeight: 'normal',

    // font weight for bold characters: 'normal' or 'bold'
    fontWeightBold: 'bold',

    // line height as a relative unit
    lineHeight: 1,

    // letter spacing as a relative unit
    letterSpacing: 0,

    // terminal cursor background color and opacity (hex, rgb, hsl, hsv, hwb or cmyk)
    cursorColor: 'rgba(248,28,229,0.8)',

    // terminal text color under BLOCK cursor
    cursorAccentColor: '#000',

    // `'BEAM'` for |, `'UNDERLINE'` for _, `'BLOCK'` for █
    cursorShape: 'BLOCK',

    // set to `true` (without backticks and without quotes) for blinking cursor
    cursorBlink: false,

    // color of the text
    foregroundColor: '#fff',

    // terminal background color
    // opacity is only supported on macOS
    backgroundColor: '#000',

    // terminal selection color
    selectionColor: 'rgba(248,28,229,0.3)',

    // border color (window, tabs)
    borderColor: '#333',

    // custom CSS to embed in the main window
    css: '',

    // custom CSS to embed in the terminal window
    termCSS: '',

    // if you're using a Linux setup which show native menus, set to false
    // default: `true` on Linux, `true` on Windows, ignored on macOS
    showHamburgerMenu: '',

    // set to `false` (without backticks and without quotes) if you want to hide the minimize, maximize and close buttons
    // additionally, set to `'left'` if you want them on the left, like in Ubuntu
    // default: `true` (without backticks and without quotes) on Windows and Linux, ignored on macOS
    showWindowControls: '',

    // custom padding (CSS format, i.e.: `top right bottom left`)
    padding: '12px 14px',

    // the full list. if you're going to provide the full color palette,
    // including the 6 x 6 color cubes and the grayscale map, just provide
    // an array here instead of a color map object
    colors: {
      black: '#000000',
      red: '#C51E14',
      green: '#1DC121',
      yellow: '#C7C329',
      blue: '#0A2FC4',
      magenta: '#C839C5',
      cyan: '#20C5C6',
      white: '#C7C7C7',
      lightBlack: '#686868',
      lightRed: '#FD6F6B',
      lightGreen: '#67F86F',
      lightYellow: '#FFFA72',
      lightBlue: '#6A76FB',
      lightMagenta: '#FD7CFC',
      lightCyan: '#68FDFE',
      lightWhite: '#FFFFFF',
    },

 

    // the shell to run when spawning a new session (i.e. /usr/local/bin/fish)
    // if left empty, your system's login shell will be used by default
    
    // Windows
    // - Make sure to use a full path if the binary name doesn't work
    // - Remove `--login` in shellArgs
    //
    // Bash on Windows
    // - Example: `C:\\Windows\\System32\\bash.exe`
    //
    // PowerShell on Windows
    // - Example: `C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`
    shell: '',

    // for setting shell arguments (i.e. for using interactive shellArgs: `['-i']`)
    // by default `['--login']` will be used
    shellArgs: ['-i'],

    // for environment variables
    env:{ TERM: 'cygwin' },



    // set to `false` for no bell
    bell: 'SOUND',

    // if `true` (without backticks and without quotes), selected text will automatically be copied to the clipboard
    copyOnSelect: false,

    // if `true` (without backticks and without quotes), hyper will be set as the default protocol client for SSH
    defaultSSHApp: true,

    // if `true` (without backticks and without quotes), on right click selected text will be copied or pasted if no
    // selection is present (`true` by default on Windows and disables the context menu feature)
    quickEdit: false,

    // choose either `'vertical'`, if you want the column mode when Option key is hold during selection (Default)
    // or `'force'`, if you want to force selection regardless of whether the terminal is in mouse events mode
    // (inside tmux or vim with mouse mode enabled for example).
    macOptionSelectionMode: 'vertical',

    // URL to custom bell
    // bellSoundURL: 'http://example.com/bell.mp3',

    // Whether to use the WebGL renderer. Set it to false to use canvas-based
    // rendering (slower, but supports transparent backgrounds)
    webGLRenderer: true,

    // for advanced config flags please refer to https://hyper.is/#cfg

    hyperTabs: {
      trafficButtons: true,
      }
  },

  // a list of plugins to fetch and install from npm
  // format: [@org/]project[#version]
  // examples:
  //   `hyperpower`
  //   `@company/project`
  //   `project#1.0.1`
  plugins: ["hyper-midnight", "hyperpower", "hyperborder", "hyper-tabs-enhanced"],

  // in development, you can create a directory under
  // `~/.hyper_plugins/local/` and include it here
  // to load it and avoid it being `npm install`ed
  localPlugins: [],

  keymaps: {
    // Example
    // 'window:devtools': 'cmd+alt+o',
  },
};

Repository:

Structure of a Django website

A Django project:

  • A single project that is split into apps (at least 1 app to a project)
  • Each app handles a self-contained function that the site needs to perform
The Django project holds some configurations that apply to the project as a whole, such as project settings, URLs, shared templates and static files. Each application can have its own database and has its own functions to control how the data is displayed to the user in HTML templates.
  • Each application also has its own URLs as well as its own HTML templates and static files (like javascript or CSS)

Model-View-Controller
Django apps are structured so that there is a separation of logic. It supports the Model-View-Controller Pattern, which is the architecture on which most web frameworks are built.
Model-View-Controller Pattern w/ Lego

The process starts with a request: the user entering a URL (requesting to view a certain page)

The request reaches the controller: The controller is responsible for grabbing all of the necessary building blocks and organizing them as necessary.

The building blocks are the models: In a web app, models help the controller retrieve all of the information it needs from the database.

The final part is the view: In a web application, the view is the final page the user sees in their browser.

  • In each application there are three separate files that handles the three main pieces of logic separately:
    • Model - defines the data structure
      • Usually a database, the base layer to an application
    • View - displays some or all of the data to the user with HTMl and CSS
    • Controller - handles how the database and the view interact
In Django, the architecture is slightly different. Although based upon the MVC pattern, Django handles the controller part itself. There’s no need to define how the database and views interact. It’s all done for you!
Model-View-Template

The view and template in the MVT pattern make up the view in the MVC pattern. Just add some URL configurations to map the views to (and then Django organises it).

*Side note: I'm deleting the local environment on my local machine to completely restart this, but most of the coding will be done in my Codespaces environment. Parts requiring access to the command prompt will be done locally (don't know how to use it on Codespaces).

Hello, World

Setting up development environment

Use the same folder names as the tutorial uses
image
We'll then create a virtual environment:
At its core, the main purpose of Python virtual environments is to create an isolated environment for Python projects. This means that each project can have its own dependencies, regardless of what dependencies every other project has

This command will create a folder venv in your working directory. Inside this directory, you’ll find several files including a copy of the Python standard library. Later, when you install new dependencies, they will also be stored in this directory.
Set up Virtual Python Env Windows
Microsoft Windows [Version 10.0.18363.1139]
(c) 2019 Microsoft Corporation. All rights reserved.

C:\Users\droid>mkdir rp-portfolio

C:\Users\droid>cd rp-portfolio

C:\Users\droid\rp-portfolio>python3 -m venv venv

C:\Users\droid\rp-portfolio>venv\Scripts\activate.bat
The system cannot find the path specified.

C:\Users\droid\rp-portfolio>C:\> venv\Scripts\activate.bat
The system cannot find the path specified.

C:\Users\droid\rp-portfolio>venv\Scripts\activate.bat
The system cannot find the path specified.

C:\Users\droid\rp-portfolio>python3 -m venv

C:\Users\droid\rp-portfolio>venv\Scripts\activate.bat
The system cannot find the path specified.

C:\Users\droid\rp-portfolio>cd ../

C:\Users\droid>mkdir django-start

C:\Users\droid>cd django-start

C:\Users\droid\django-start>python3 -m venv venv

C:\Users\droid\django-start>venv\Scripts\activate.bat
The system cannot find the path specified.

C:\Users\droid\django-start>pip install django
Requirement already satisfied: django in c:\users\droid\appdata\local\programs\python\python37\lib\site-packages (3.0.5)
Requirement already satisfied: pytz in c:\users\droid\appdata\local\programs\python\python37\lib\site-packages (from django) (2020.1)
Requirement already satisfied: asgiref~=3.2 in c:\users\droid\appdata\local\programs\python\python37\lib\site-packages (from django) (3.2.7)
Requirement already satisfied: sqlparse>=0.2.2 in c:\users\droid\appdata\local\programs\python\python37\lib\site-packages (from django) (0.3.1)

C:\Users\droid\django-start>python3

C:\Users\droid\django-start>pip

Usage:
  pip <command> [options]

Commands:
  install                     Install packages.
  download                    Download packages.
  uninstall                   Uninstall packages.
  freeze                      Output installed packages in requirements format.
  list                        List installed packages.
  show                        Show information about installed packages.
  check                       Verify installed packages have compatible dependencies.
  config                      Manage local and global configuration.
  search                      Search PyPI for packages.
  cache                       Inspect and manage pip's wheel cache.
  wheel                       Build wheels from your requirements.
  hash                        Compute hashes of package archives.
  completion                  A helper command used for command completion.
  debug                       Show information useful for debugging.
  help                        Show help for commands.

General Options:
  -h, --help                  Show help.
  --isolated                  Run pip in an isolated mode, ignoring environment variables and user configuration.
  -v, --verbose               Give more output. Option is additive, and can be used up to 3 times.
  -V, --version               Show version and exit.
  -q, --quiet                 Give less output. Option is additive, and can be used up to 3 times (corresponding to WARNING, ERROR, and CRITICAL logging levels).
  --log <path>                Path to a verbose appending log.
  --no-input                  Disable prompting for input.
  --proxy <proxy>             Specify a proxy in the form [user:passwd@]proxy.server:port.
  --retries <retries>         Maximum number of retries each connection should attempt (default 5 times).
  --timeout <sec>             Set the socket timeout (default 15 seconds).
  --exists-action <action>    Default action when a path already exists: (s)witch, (i)gnore, (w)ipe, (b)ackup, (a)bort.
  --trusted-host <hostname>   Mark this host or host:port pair as trusted, even though it does not have valid or any HTTPS.
  --cert <path>               Path to alternate CA bundle.
  --client-cert <path>        Path to SSL client certificate, a single file containing the private key and the certificate in PEM format.
  --cache-dir <dir>           Store the cache data in <dir>.
  --no-cache-dir              Disable the cache.
  --disable-pip-version-check
                              Don't periodically check PyPI to determine whether a new version of pip is available for download. Implied with --no-index.
  --no-color                  Suppress colored output
  --no-python-version-warning
                              Silence deprecation warnings for upcoming unsupported Pythons.
  --use-feature <feature>     Enable new functionality, that may be backward incompatible.
  --use-deprecated <feature>  Enable deprecated functionality, that will be removed in the future.

C:\Users\droid\django-start>pip install virtualenv
Requirement already satisfied: virtualenv in c:\users\droid\appdata\local\programs\python\python37\lib\site-packages (20.0.18)
Requirement already satisfied: distlib<1,>=0.3.0 in c:\users\droid\appdata\local\programs\python\python37\lib\site-packages (from virtualenv) (0.3.0)
Requirement already satisfied: appdirs<2,>=1.4.3 in c:\users\droid\appdata\local\programs\python\python37\lib\site-packages (from virtualenv) (1.4.3)
Requirement already satisfied: six<2,>=1.9.0 in c:\users\droid\appdata\local\programs\python\python37\lib\site-packages (from virtualenv) (1.14.0)
Requirement already satisfied: filelock<4,>=3.0.0 in c:\users\droid\appdata\local\programs\python\python37\lib\site-packages (from virtualenv) (3.0.12)
Requirement already satisfied: importlib-metadata<2,>=0.12; python_version < "3.8" in c:\users\droid\appdata\local\programs\python\python37\lib\site-packages (from virtualenv) (1.6.0)
Requirement already satisfied: zipp>=0.5 in c:\users\droid\appdata\local\programs\python\python37\lib\site-packages (from importlib-metadata<2,>=0.12; python_version < "3.8"->virtualenv) (3.1.0)

C:\Users\droid\django-start>python3 -m venv

C:\Users\droid\django-start>python3 -m venv venv

C:\Users\droid\django-start>python 3 venv
python: can't open file '3': [Errno 2] No such file or directory

C:\Users\droid\django-start>virtualenv --python C:\Users\droid\AppData\Local\Programs\Python\Python37\python.exe venv
created virtual environment CPython3.7.7.final.0-64 in 7931ms
  creator CPython3Windows(dest=C:\Users\droid\django-start\venv, clear=False, global=False)
  seeder FromAppData(download=False, pip=latest, setuptools=latest, wheel=latest, via=copy, app_data_dir=C:\Users\droid\AppData\Local\pypa\virtualenv\seed-app-data\v1.0.1)
  activators BashActivator,BatchActivator,FishActivator,PowerShellActivator,PythonActivator,XonshActivator

C:\Users\droid\django-start>

image
image

Django will be installed on this virtual environment:

image
Your virtual environment directory doesn’t have to be called venv. If you want to create one under a different name, for example my_venv, just replace with the second venv with my_venv. Then, when activating your virtual environment, replace venv with my_venv again. The prompt will also now be prefixed with (my_venv).

Create a Django Project

A Django project is made up of a project and its constituent apps. Create the first project:
image
This will create a new directory personal_portfolio. If you cd into this new directory you’ll see another directory called personal_portfolio and a file called manage.py. Your directory structure should look something like this:

Move the files around so the file structure is like this:

Run the server with this command:

Congratulations, you’ve created a Django site! The source code for this part of the tutorial can be found on GitHub. The next step is to create apps so that you can add views and functionality to your site.
image
image

image

image

Create a Django application

For this part of the tutorial, we’ll create an app called hello_world, which you’ll subsequently delete as its not necessary for our personal portfolio site.
Create the app: $ python manage.py startapp hello_world

This creates a new directory in the root folder (personal_portfolio) with these files:

  • __init__.py tells Python to treat the directory as a Python package.
  • admin.py contains settings for the Django admin pages.
  • apps.py contains settings for the application configuration.
  • models.py contains a series of classes that Django’s ORM converts to database tables.
  • tests.py contains test classes.
  • views.py contains functions and classes that handle what data is displayed in the HTML templates.
image
We then need to install the application in our project:

In settings.py (in rp-portfolio), add the following code:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'hello_world',
]
image
That line of code means that your project now knows that the app you just created exists.

Create a view

  • In Django, views are collections of functions or classes inside the views.py file in your app directory
  • Each function or class handles the logic that gets processed each time a different URL is visited

Navigate to hello-world/views.py inside the Django project (the whole folder). We already have the line of code that imports the {Render()}:
image
Now we're going to define the 'hello world' request in views.py:

We've defined a view called hello_world() - when this function is called, it will render an html file called hello_world.html (which is yet to be created)

image

The view hello_world takes on one argument - request. This object is an HttpRequestObject that is created whenever a page loads.

  • It contains information about the request -
    • Method (values such as GET & POST (REST API)
Create the HTML template to display to the user

render() looks for HTML templates inside a directory called templates inside your app directory.

Create the directory inside the hello_world application:

image

Create a file inside the templates directory, called <h1>Hello World!</h1>

image

You’ve now created a function to handle your views and templates to display to the user. The final step is to hook up your URLs so that you can visit the page you’ve just created. Your project has a module called urls.py in which you need to include a URL configuration for the hello_world app. Inside personal_portfolio/urls.py, add the following:
image
Explanation (urls.py)

This looks for a module called urls.py inside the hello_world application and registers any URLs defined there.

Whenever you visit the root path of your url (localhost:8000) the hello_world application's URLs will be registerd.

The hello_world.urls module doesn't exist yet - so create it by creating a new file - hello_world/urls.py and entering the following code:

image
image

Now re-running the server will provide:

image

Add Bootstrap to the app

Base Template
Before we get started with the Bootstrap styles, we’ll create a base template that we can import to each subsequent view. This template is where we’ll subsequently add the Bootstrap style imports.

Inside personal_portfolio, create (mkdir) a new directory called templates and a file in that directory called base.html

We create this additional templates directory to store HTML templates that will be used in every Django app in the project. As you saw previously, each Django project can consist of multiple apps that handle separated logic, and each app contains its own templates directory to store HTML templates related to the application.
This application structure works well for the back end logic, but we want our entire site to look consistent on the front end. Instead of having to import Bootstrap styles into every app, we can create a template or set of templates that are shared by all the apps. As long as Django knows to look for templates in this new, shared directory it can save a lot of repeated styles.

image
image
What happens here is that any HTML inside the page_content block gets added inside the same block in base.html.

Bootstrap CDN

Bootstrap CDN
All future templates that we create will extend base.html so that we can include Bootstrap styling on every page without having to import the styles again.
Before we can see our new styled application, we need to tell our Django project that base.html exists. The default settings register template directories in each app, but not in the project directory itself. In personal_portfolio/settings.py, update TEMPLATES:

image
Whenever you want to create templates or import scripts that you intend to use in all your Django apps inside a project, you can add them to this project-level directory and extend them inside your app templates. Adding templates is the last stage to building your Hello, World! Django site. You learned how the Django templating engine works and how to create project-level templates that can be shared by all the apps inside your Django project. In this section, you learned how to create a simple Hello, World! Django site by creating a project with a single app. In the next section, you’ll create another application to showcase web development projects, and you’ll learn all about models in Django!

image

Showcase your projects

We'll create another Django app - projects - to hold a sample of projects that will be displayed to the user.

Deleting the hello_world application: cd personal_portfolio and settings.py before removing this line: 'hello_world', # Delete this line

(Without this line the localhost:8000 link breaks)

Then remove the URL path in urls.py: path('', include('hello_world.urls')), # Delete this line

cd back into django-start and run the following command to create the projects app:

image

cd into personal_portfolio and then open settings.py, installing the projects app:

image
Terminal code for this phase
Microsoft Windows [Version 10.0.18363.1139]
(c) 2019 Microsoft Corporation. All rights reserved.

C:\Users\droid>cd rp-portfolio

C:\Users\droid\rp-portfolio>cd django-start

C:\Users\droid\rp-portfolio\django-start>venv\Scripts\activate.bat

(venv) C:\Users\droid\rp-portfolio\django-start>settings.py
'settings.py' is not recognized as an internal or external command,
operable program or batch file.

(venv) C:\Users\droid\rp-portfolio\django-start>cd personal-portfolio
The system cannot find the path specified.

(venv) C:\Users\droid\rp-portfolio\django-start>cd personal_portfolio

(venv) C:\Users\droid\rp-portfolio\django-start\personal_portfolio>settings.py

(venv) C:\Users\droid\rp-portfolio\django-start\personal_portfolio>
[main 2020-10-23T05:04:00.705Z] update#setState idle
(node:16404) Electron: Loading non-context-aware native module in renderer: '\\?\C:\Users\droid\AppData\Local\Programs\Microsoft VS Code\resources\app\node_modules.asar.unpacked\vscode-sqlite3\build\Release\sqlite.node'. This is deprecated, see https://github.com/electron/electron/issues/18397.
(node:1324) Electron: Loading non-context-aware native module in renderer: '\\?\C:\Users\droid\AppData\Local\Programs\Microsoft VS Code\resources\app\node_modules.asar.unpacked\spdlog\build\Release\spdlog.node'. This is deprecated, see https://github.com/electron/electron/issues/18397.
(node:16404) Electron: Loading non-context-aware native module in renderer: '\\?\C:\Users\droid\AppData\Local\Programs\Microsoft VS Code\resources\app\node_modules.asar.unpacked\spdlog\build\Release\spdlog.node'. This is deprecated, see https://github.com/electron/electron/issues/18397.
(node:1324) [DEP0005] DeprecationWarning: Buffer() is deprecated due to security and usability issues. Please use the Buffer.alloc(), Buffer.allocUnsafe(), or Buffer.from() methods instead.
[main 2020-10-23T05:04:30.708Z] update#setState checking for updates
[main 2020-10-23T05:04:31.761Z] update#setState idle


(venv) C:\Users\droid\rp-portfolio\django-start\personal_portfolio>cd ../

(venv) C:\Users\droid\rp-portfolio\django-start>python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).

You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
October 23, 2020 - 13:05:48
Django version 3.1.2, using settings 'personal_portfolio.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
[23/Oct/2020 13:05:57] "GET / HTTP/1.1" 200 24

(venv) C:\Users\droid\rp-portfolio\django-start>cd personal_portfolio

(venv) C:\Users\droid\rp-portfolio\django-start\personal_portfolio>settings.py

(venv) C:\Users\droid\rp-portfolio\django-start\personal_portfolio>
[main 2020-10-23T05:06:40.142Z] update#setState idle
(node:18036) Electron: Loading non-context-aware native module in renderer: '\\?\C:\Users\droid\AppData\Local\Programs\Microsoft VS Code\resources\app\node_modules.asar.unpacked\vscode-sqlite3\build\Release\sqlite.node'. This is deprecated, see https://github.com/electron/electron/issues/18397.
(node:18036) Electron: Loading non-context-aware native module in renderer: '\\?\C:\Users\droid\AppData\Local\Programs\Microsoft VS Code\resources\app\node_modules.asar.unpacked\spdlog\build\Release\spdlog.node'. This is deprecated, see https://github.com/electron/electron/issues/18397.
(node:18000) Electron: Loading non-context-aware native module in renderer: '\\?\C:\Users\droid\AppData\Local\Programs\Microsoft VS Code\resources\app\node_modules.asar.unpacked\spdlog\build\Release\spdlog.node'. This is deprecated, see https://github.com/electron/electron/issues/18397.
[main 2020-10-23T05:07:10.144Z] update#setState checking for updates
[main 2020-10-23T05:07:11.523Z] update#setState idle


(venv) C:\Users\droid\rp-portfolio\django-start\personal_portfolio>urls.py

(venv) C:\Users\droid\rp-portfolio\django-start\personal_portfolio>
[main 2020-10-23T05:07:22.120Z] update#setState idle
(node:11012) Electron: Loading non-context-aware native module in renderer: '\\?\C:\Users\droid\AppData\Local\Programs\Microsoft VS Code\resources\app\node_modules.asar.unpacked\vscode-sqlite3\build\Release\sqlite.node'. This is deprecated, see https://github.com/electron/electron/issues/18397.
(node:11012) Electron: Loading non-context-aware native module in renderer: '\\?\C:\Users\droid\AppData\Local\Programs\Microsoft VS Code\resources\app\node_modules.asar.unpacked\spdlog\build\Release\spdlog.node'. This is deprecated, see https://github.com/electron/electron/issues/18397.
(node:11412) Electron: Loading non-context-aware native module in renderer: '\\?\C:\Users\droid\AppData\Local\Programs\Microsoft VS Code\resources\app\node_modules.asar.unpacked\spdlog\build\Release\spdlog.node'. This is deprecated, see https://github.com/electron/electron/issues/18397.
[main 2020-10-23T05:07:52.124Z] update#setState checking for updates
[main 2020-10-23T05:07:52.948Z] update#setState idle


(venv) C:\Users\droid\rp-portfolio\django-start\personal_portfolio>cd ../

(venv) C:\Users\droid\rp-portfolio\django-start>python manage.py startapp projects

(venv) C:\Users\droid\rp-portfolio\django-start>cd personal_portfolio

(venv) C:\Users\droid\rp-portfolio\django-start\personal_portfolio>settings.py

(venv) C:\Users\droid\rp-portfolio\django-start\personal_portfolio>
[main 2020-10-23T05:12:32.124Z] update#setState idle
(node:17584) Electron: Loading non-context-aware native module in renderer: '\\?\C:\Users\droid\AppData\Local\Programs\Microsoft VS Code\resources\app\node_modules.asar.unpacked\vscode-sqlite3\build\Release\sqlite.node'. This is deprecated, see https://github.com/electron/electron/issues/18397.
^A^A^A^A^A^A^A(node:14676) Electron: Loading non-context-aware native module in renderer: '\\?\C:\Users\droid\AppData\Local\Programs\Microsoft VS Code\resources\app\node_modules.asar.unpacked\spdlog\build\Release\spdlog.node'. This is deprecated, see https://github.com/electron/electron/issues/18397.
(node:17584) Electron: Loading non-context-aware native module in renderer: '\\?\C:\Users\droid\AppData\Local\Programs\Microsoft VS Code\resources\app\node_modules.asar.unpacked\spdlog\build\Release\spdlog.node'. This is deprecated, see https://github.com/electron/electron/issues/18397.

Projects App: Models

We use ORMs to create models
If you want to store data to display on a website, then you’ll need a database. Typically, if you want to create a database with tables and columns within those tables, you’ll need to use SQL to manage the database. But when you use Django, you don’t need to learn a new language because it has a built-in Object Relational Mapper (ORM).
  • ORM - a program that allows you to create classes that correspond to database tables
    • Class attributes correspond to columns
    • Class instances correspond to [database/table] rows
  • We can therefore just write some Python classes rather than having to use SQL

The classes we build to represent database tables are called models. They live in models.py of each Django app.

In your projects app, you’ll only need one table to store the different projects you’ll display to the user. That means you’ll only need to create one model in models.py.
We'll create a model called Project and it will have the following fields:
  • title - short string field to hold the name of the project
  • description - a larger string field to hold a longer piece of text (the description of the project)
  • technology - a string field that will be limited to only a small selection of options
  • image - an image field that holds the file path where the image is stored

We'll create a new class in models.py -

Django models come in with many built in model field types. Only three have been used in this snippet: CharField, TextField, and FilePathField:

  • CharField is used for short strings and specifies a maximum length
  • TextField is similar to CharField but as it doesn't specify a maximum length it can be used to hold longer strings
  • FilePathField is also a string, but it must point to a file location (for example the image)
We've created the Project class so now we need Django to create the database
  • By default, the Django ORM creates databases in SQLite, but you can use other databases that use the SQL language

To start creating the database, we need to create a migration - a file containing a Migration class with rules that tell Django what changes need to be made to the database. Activate your Virtual Environment - venv and enter the following code into your terminal:

image
You should see that a file projects/migrations/0001_initial.py has been created in the projects app. Check out that file in the source code to make sure your migration is correct. Now that you’ve create a migration file, you need to apply the migrations set out in the migrations file and create your database using the migrate command:

image
Migrations note
Note: When running both the makemigrations and migrate commands, we added projects to our command. This tells Django to only look at models and migrations in the projects app. Django comes with several models already created. If you run makemigrations and migrate without the projects flag, then all migrations for all the default models in your Django projects will be created and applied. This is not a problem, but for the purposes of this section, they are not needed.
New files
You should also see that a file called db.sqlite3 has been created in the root of your project. Now your database is set up and ready to go. You can now create rows in your table that are the various projects you want to show on your portfolio site.
Create instances of the Project class using the Django shell

To use the Django shell, enter this command into the terminal while in the root folder:

image
Once you’ve accessed the shell, you’ll notice that the command prompt will change from $ to >>>. You can then import your models
image

Update (26/10/2020) - See comment here

Think i fixed it -

Microsoft Windows [Version 10.0.18363.1139]
(c) 2019 Microsoft Corporation. All rights reserved.

C:\Users\droid>cd django-start

C:\Users\droid\django-start>cd django-start
The system cannot find the path specified.

C:\Users\droid\django-start>cd ../

C:\Users\droid>cd rp-portfolio

C:\Users\droid\rp-portfolio>cd django-start

C:\Users\droid\rp-portfolio\django-start>venv\Scripts\activate.bat

(venv) C:\Users\droid\rp-portfolio\django-start>python manage.py makemigrations projects
No changes detected in app 'projects'

(venv) C:\Users\droid\rp-portfolio\django-start>python manage.py migrate projects
Operations to perform:
  Apply all migrations: projects
Running migrations:
  Applying projects.0001_initial... OK

(venv) C:\Users\droid\rp-portfolio\django-start>
27/10/2020 - Django Shell (working!)
Microsoft Windows [Version 10.0.18363.1139]
(c) 2019 Microsoft Corporation. All rights reserved.

C:\Users\droid>cd django-start

C:\Users\droid\django-start>cd django-start
The system cannot find the path specified.

C:\Users\droid\django-start>cd ../

C:\Users\droid>cd rp-portfolio

C:\Users\droid\rp-portfolio>cd ../

C:\Users\droid>cd rp-portfolio

C:\Users\droid\rp-portfolio>cd django-start

C:\Users\droid\rp-portfolio\django-start>venv\Scripts\activate.bat

(venv) C:\Users\droid\rp-portfolio\django-start>python manage.py shell
Python 3.7.7 (tags/v3.7.7:d7c567b08f, Mar 10 2020, 10:41:24) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>

Microsoft Windows [Version 10.0.18363.1139]
(c) 2019 Microsoft Corporation. All rights reserved.

C:\Users\droid>cd django-start

C:\Users\droid\django-start>cd django-start
The system cannot find the path specified.

C:\Users\droid\django-start>cd ../

C:\Users\droid>cd rp-portfolio

C:\Users\droid\rp-portfolio>cd ../

C:\Users\droid>cd rp-portfolio

C:\Users\droid\rp-portfolio>cd django-start

C:\Users\droid\rp-portfolio\django-start>venv\Scripts\activate.bat

(venv) C:\Users\droid\rp-portfolio\django-start>python manage.py shell
Python 3.7.7 (tags/v3.7.7:d7c567b08f, Mar 10 2020, 10:41:24) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from projects.models import Project
>>> ^A^A

Now we import Project from the models and then create our first project:

The new project we create will contain the following attributes:

  • Name: My First Project
  • Description: A web develpment project
  • Technology: Django
  • Image: img/project1.png

Let's create an instance of the Project class in the Python shell:

image
image

image

Projects App - Views

views.py in django-start/projects (Projects App) 📁
Now you’ve created the projects to display on your portfolio site, you’ll need to create view functions to send the data from the database to the HTML templates.

Create two views in the Projects app:

  • Index view - shows a snippet of information about each project
  • Detail view - shows more information on a particular topic (the project)

INDEX View:

Inside views.py file -

  • Import Project class from models.py, create a function - project_index()
  • project_index() function renders a template called project_index.html with the context
  • The body of the function uses the Django ORM query to select all objects in the Project table:
Code with comments:
from django.shortcuts import render # import render CLASS from Django shortcuts module
from projects.models import Project # Import Models CLASS (db/sql3) from Project Models

def project_index(request): # define the project_index class (using request as a param/parent)
    projects = Project.objects.all() # set what projects (like a variable) is - all projects // # query to select all objects in the `Project/#` table
    context = { # Makes use of the Django ORM // Define dictionary `context` :-> The dictionary only has one entry `projects` to which we assign our Queryset containing all projects. The context dictionary is used to send information to our template. Every view function you create needs to have a context dictionary.
        'projects': projects 
    }
    return render(request, 'project_index.html', context) # In line 9, context is added as an argument to render(). Any entries in the context dictionary are available in the template, as long as the context argument is passed to render(). You’ll need to create a context dictionary and pass it to render in each view function you create.

image

Now we need to create the project_detail() view function. It will contain an additional argument - the id of the project that is being viewed.
def project_detail(request, pk): # define the `project_detail` function with the `pk` (id) and request parameters for the project [object/orm]
  project = Project.objects.get(pk=pk) # set what the `project` is - gathering the objects of `Project`. /#/ In line 14, we perform another query. This query retrieves the project with primary key, pk, equal to that in the function argument. We then assign that project in our context dictionary, which we pass to render(). Again, there’s a template project_detail.html, which we have yet to create.
  context = { # set the context - see above variable
    'project': project
  }
  return render(request, 'project_details.html', context)
image

Now we're going to hook up our views to the urls of our project 📂
Create a file projects/urls.py → this will hold the url configuration for the app Projects
from django.urls import path # import path from Django urls - module for file paths
from . import views # import views.py

urlpatterns = [ 
    path("", views.project_index, name="project_index"), # hook up the root URL of our APP to the `project_index` view
    path("<int:pk>/", views.project_detail, name="project_detail"), # hook up the `project_detail` view -> generate the URL (using the pk attribute) depending on which project[_detail] you want to view // To do this, we used the <int:pk> notation. This just tells Django that the value passed in the URL is an integer, and its variable name is pk.
]
We now need to hook these URLs up to the Project URLs

Go to personal_portfolio/URLS.py and add this code:

from django.contrib import admin # import Django admin from contrib
from django.urls import path, include # import django path, include for url management from django[.urls]

urlpatterns = [ # the path [+] setup for the urls
    path("admin/", admin.site.urls), # create a path called "admin" that uses 'admin.site.urls'
    path("projects/", include("projects.urls")), # create a path called "projects" which uses the "projects.urls" specified in the 'projects' app urls.py file
]

path("projects/", include("projects.urls")) - This line of code includes all the URLs in the projects app but means they are accessed when prefixed by projects/. There are now two full URLs that can be accessed with our project:

  • localhost:8000/projects: The project index page
  • localhost:8000/projects/3: The detail view for the project with pk=3
These URLs still won’t work properly because we don’t have any HTML templates. But our views and logic are up and running so all that’s left to do is create those templates. If you want to check your code, take a look at the source code for this section.

Projects App - Templates

We now need to create two templates -

  • The project_index template
  • The project_detail template

We can use the Bootstrap styles we added to our application/project to create some pre-styled (bootstrap) components to make the views look nice.

Starting with the project_index template:

Well create a grid of Bootstrap Cards, each card will display information about that project.

We don’t want to have to create 100 different Bootstrap cards and hard-code in all the information to each project. Instead, we’re going to use a feature of the Django template engine: for loops.

For Loops Info
Using this feature, you’ll be able to loop through all the projects and create a card for each one. The for loop syntax in the Django template engine is as follows:
{% for project in projects %}
{# Do something... #}
{% endfor %}
Add this code to projects/templates/project_index.html
{% extends "base.html" %} # extent base.html, like in the Hello, World! app /#/ https://github.com/realpython/materials/blob/get-started-with-django-part-1/rp-portfolio/personal_portfolio/templates/base.html
{% load static %} # tag to include static files like images. The filepath when we created the models in projects will be used for these images
{% block page_content %}
<h1>Projects</h1>
<div class="row">
{% for project in projects %} # begin for loop - looping over all projects passed in by the context dictionary
    <div class="col-md-4">
        <div class="card mb-2">
            <img class="card-img-top" src="{% static project.image %}">
            <div class="card-body">
                <h5 class="card-title">{{ project.title }}</h5> # fill in content from models attributes
                <p class="card-text">{{ project.description }}</p>
                <a href="{% url 'project_detail' project.pk %}"
                   class="btn btn-primary"> # create a button, linking to the project's page (project_detail pk)
                    Read More
                </a>
            </div>
        </div>
    </div>
    {% endfor %}
</div>
{% endblock %}

line 2 -

Django automatically registers static files stored in a directory named static/ in each application. Our image file path names were of the structure: img/<photo_name>.png. When loading static files, Django looks in the static/ directory for files matching a given filepath within static/. So, we need to create a directory named static/ with another directory named img/ inside. Inside img/, you can copy over the images from the source code on GitHub.

Visit localhost:8000/projects

line 1 → We need to extend the base.html file - to include a navbar and a Bootstrap container to contain all the content
<!--https://github.com/Gizmotronn/django-start/commit/269aa23777191982e46aee06d30be0b1b3b04de8-->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">

<nav class="navbar navbar-expand-lg navbar-light bg-light">
    <div class="container">
        <a class="navbar-brand" href="{% url 'project_index' %}">RP Portfolio</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
          <span class="navbar-toggler-icon"></span>
        </button>

        <div class="collapse navbar-collapse" id="navbarSupportedContent">
          <ul class="navbar-nav mr-auto">
            <li class="nav-item active">
              <a class="nav-link" href="{% url 'project_index' %}">Home</a>
            </li>
            <li class="nav-item">
              <a class="nav-link" href="{% url 'blog_index' %}">Blog</a>
            </li>
          </ul>
        </div>
    </div>

</nav>

<div class="container">
    {% block page_content %}{% endblock %}
</div>

<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
line 2 → Create a directory called /projects/static [aka inside the project app] for the static files (aka images)
image

We're then going to download these images and put them into /static/img as static/image files

line 6 → Begin the for loop

Looping over all the projects passed in by the context dictionary

  • We can access each individual project inside this for loop
To access the project’s attributes, you can use dot notation inside double curly brackets

{{ project.title }} —> Project Title attribute

Line 6 → {% for project in projects %}

line 9 → Project image

Include our project image

  • Inside the src attribute we include {% static project.image %}
  • This tells Django to look inside the static files to find a file matching project.image (i.e. the file name inside the python directory
line 13 → Link to our project_detail

Takes integer arguments

  • Accessing URLs in Django is simila to accessing static files

Code for the URL [form]: {% url '<url path name>' <view_function_arguments> %}

  • We are accessing a URL path named project_detail - it takes integer arguments corresponding to the pk of the project

At this point, if we view localhost:8000, we see this:

image
Moving on to the project_detail template (e.g. link localhost:8000/projects/1:
{% extends "base.html" %} <!--Copies context from `base.html`-->
{% load static %}

{% block page_content %}
<h1>{{ project.title }}</h1>
<div class="row">
    <div class="col-md-8">
        <img src="{% static project.image %}" alt="" width="100%">
    </div>
    <div class="col-md-4">
        <h5>About the project:</h5>
        <p>{{ project.description }}</p>
        <br>
        <h5>Technology used:</h5>
        <p>{{ project.technology }}</p>
    </div>
</div>
{% endblock %}
The code in this template has the same functionality as each project card in the project_index.html template. The only difference is the introduction of some Bootstrap columns.

image

Share your knowledge with a blog

This fully functional blog with Django will be able to perform the following tasks:

  • Create, update, and delete blog posts
  • Display posts to the user as either an index view or a detail view
  • Assign categories to posts
  • Allow users to comment on posts

First, let's create a new application in Django:
cd django-start # repo

cd django-start

venv\Scripts\activate.bat

python manage.py startapp blog
Then we need to go into personal_portfolio/settings.py and enter this code to install the app:
INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "projects",
    "blog",
]

Here's mine:

"""
Django settings for personal_portfolio project.

Generated by 'django-admin startproject' using Django 3.1.2.

For more information on this file, see
https://docs.djangoproject.com/en/3.1/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.1/ref/settings/
"""

from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '4#!%rchx+!0+fcbskjuv6cwf85v2a2ktvey1z!_qwq!!vc33j0'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # 'hello_world',
    'projects',
    'blog'
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'personal_portfolio.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        "DIRS": ["personal_portfolio/templates/"],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'personal_portfolio.wsgi.application'


# Database
# https://docs.djangoproject.com/en/3.1/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}


# Password validation
# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/3.1/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.1/howto/static-files/

STATIC_URL = '/static/'

Blog app: Models

We'll need three separate database tables for our blog:

  • Post
  • Category
  • Comment

See https://realpython.com/get-started-with-django-1/#blog-app-models

Here's the code for the Category and Post models (enter in blog/models.py):

from django.db import models # import models from django (database) module

class Category(models.Model): # create a class (of type model) called Category
	name = models.CharField(max_length=20) # there's one field - name - and it is of type CharField with a max character length of 20

class Post(models.Model): # create a class (of type models) called Post
	title = models.CharField(max_length=255) # attribute/field called title with a max length of 225 characters (CharField)
  body = models.TextField() # attribute called body of type TextField - no max length
  created_on = models.DateTimeField(auto_now_add=True) # attribute for the date published
  last_modified = models.DateTimeField(auto_now=True) # attribute for when it was last edited (it = post)
  categories = models.ManyToManyField('Category', related_name='posts') # category attribute relating to 'category' model
The next two fields, created_on and last_modified, are Django DateTimeFields. These store a datetime object containing the date and time when the post was created and modified respectively.
On line 9, the DateTimeField takes an argument auto_now_add=True. This assigns the current date and time to this field whenever an instance of this class is created. On line 10, the DateTimeField takes an argument auto_now=True. This assigns the current date and time to this field whenever an instance of this class is saved. That means whenever you edit an instance of this class, the date_modified is updated.

The final field on the post model is the most interesting. We want to link our models for categories and posts in such a way that many categories can be assigned to many posts. Luckily, Django makes this easier for us by providing a ManytoManyField field type. This field links the Post and Category models and allows us to create a relationship between the two tables. The ManyToManyField takes two arguments. The first is the model with which the relationship is, in this case its Category. The second allows us to access the relationship from a Category object, even though we haven’t added a field there. By adding a related_name of posts, we can access category.posts to give us a list of posts with that category.

Now let's work on the comments model (one post has many comments):

class Comment(models.Model): # create a model called Comment
    author = models.CharField(max_length=60) # attribute for comment author
    body = models.TextField() # attribute for the body of the comment
    created_on = models.DateTimeField(auto_now_add=True) # time of comment
    post = models.ForeignKey('Post', on_delete=models.CASCADE) # attribute linking to the post
On line 20, we use another relational field, the ForeignKey field. This is similar to the ManyToManyField but instead defines a many to one relationship. The reasoning behind this is that many comments can be assigned to one post. But you can’t have a comment that corresponds to many posts.
The ForeignKey field takes two arguments. The first is the other model in the relationship, in this case, Post. The second tells Django what to do when a post is deleted. If a post is deleted, then we don’t want the comments related to it hanging around. We, therefore, want to delete them as well, so we add the argument on_delete=models.CASCADE.
Now that we've created the models, we now need to create the migration files:
python manage.py makemigrations blog
The final step is to migrate the tables. This time, don’t add the app-specific flag. Later on, you’ll need the User model that Django creates for you:
$ python manage.py migrate
*Due to issues with these commands (due to the structure of my files) I'm downloading the files from the site https://github.com/realpython/materials/tree/6fdccb8ae85c5792e3639c406be09abc2d4803d8 and then continuing
Microsoft Windows [Version 10.0.18363.1139]
(c) 2019 Microsoft Corporation. All rights reserved.

C:\Users\droid>cd rp-portfolio

C:\Users\droid\rp-portfolio>cd django-start

C:\Users\droid\rp-portfolio\django-start>venv\Scripts\activate.bat

(venv) C:\Users\droid\rp-portfolio\django-start>python manage.py makemigrations blog
Traceback (most recent call last):
  File "manage.py", line 22, in <module>
    main()
  File "manage.py", line 18, in main
    execute_from_command_line(sys.argv)
  File "C:\Users\droid\django-start\venv\lib\site-packages\django\core\management\__init__.py", line 401, in execute_from_command_line
    utility.execute()
  File "C:\Users\droid\django-start\venv\lib\site-packages\django\core\management\__init__.py", line 377, in execute
    django.setup()
  File "C:\Users\droid\django-start\venv\lib\site-packages\django\__init__.py", line 24, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "C:\Users\droid\django-start\venv\lib\site-packages\django\apps\registry.py", line 114, in populate
    app_config.import_models()
  File "C:\Users\droid\django-start\venv\lib\site-packages\django\apps\config.py", line 211, in import_models
    self.models_module = import_module(models_module_name)
  File "c:\users\droid\appdata\local\programs\python\python37\lib\importlib\__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1006, in _gcd_import
  File "<frozen importlib._bootstrap>", line 983, in _find_and_load
  File "<frozen importlib._bootstrap>", line 967, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 677, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 724, in exec_module
  File "<frozen importlib._bootstrap_external>", line 860, in get_code
  File "<frozen importlib._bootstrap_external>", line 791, in source_to_code
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "C:\Users\droid\rp-portfolio\django-start\blog\models.py", line 8
    body = models.TextField() # attribute called body of type TextField - no max length
                                                                                      ^
IndentationError: unindent does not match any outer indentation level

(venv) C:\Users\droid\rp-portfolio\django-start>python manage.py makemigrations blog
Traceback (most recent call last):
  File "manage.py", line 22, in <module>
    main()
  File "manage.py", line 18, in main
    execute_from_command_line(sys.argv)
  File "C:\Users\droid\django-start\venv\lib\site-packages\django\core\management\__init__.py", line 401, in execute_from_command_line
    utility.execute()
  File "C:\Users\droid\django-start\venv\lib\site-packages\django\core\management\__init__.py", line 377, in execute
    django.setup()
  File "C:\Users\droid\django-start\venv\lib\site-packages\django\__init__.py", line 24, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "C:\Users\droid\django-start\venv\lib\site-packages\django\apps\registry.py", line 114, in populate
    app_config.import_models()
  File "C:\Users\droid\django-start\venv\lib\site-packages\django\apps\config.py", line 211, in import_models
    self.models_module = import_module(models_module_name)
  File "c:\users\droid\appdata\local\programs\python\python37\lib\importlib\__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1006, in _gcd_import
  File "<frozen importlib._bootstrap>", line 983, in _find_and_load
  File "<frozen importlib._bootstrap>", line 967, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 677, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 724, in exec_module
  File "<frozen importlib._bootstrap_external>", line 860, in get_code
  File "<frozen importlib._bootstrap_external>", line 791, in source_to_code
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "C:\Users\droid\rp-portfolio\django-start\blog\models.py", line 8
    body = models.TextField() # attribute called body of type TextField - no max length
                                                                                      ^
IndentationError: unindent does not match any outer indentation level

(venv) C:\Users\droid\rp-portfolio\django-start>python manage.py migrate
Traceback (most recent call last):
  File "manage.py", line 22, in <module>
    main()
  File "manage.py", line 18, in main
    execute_from_command_line(sys.argv)
  File "C:\Users\droid\django-start\venv\lib\site-packages\django\core\management\__init__.py", line 401, in execute_from_command_line
    utility.execute()
  File "C:\Users\droid\django-start\venv\lib\site-packages\django\core\management\__init__.py", line 377, in execute
    django.setup()
  File "C:\Users\droid\django-start\venv\lib\site-packages\django\__init__.py", line 24, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "C:\Users\droid\django-start\venv\lib\site-packages\django\apps\registry.py", line 114, in populate
    app_config.import_models()
  File "C:\Users\droid\django-start\venv\lib\site-packages\django\apps\config.py", line 211, in import_models
    self.models_module = import_module(models_module_name)
  File "c:\users\droid\appdata\local\programs\python\python37\lib\importlib\__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1006, in _gcd_import
  File "<frozen importlib._bootstrap>", line 983, in _find_and_load
  File "<frozen importlib._bootstrap>", line 967, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 677, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 724, in exec_module
  File "<frozen importlib._bootstrap_external>", line 860, in get_code
  File "<frozen importlib._bootstrap_external>", line 791, in source_to_code
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "C:\Users\droid\rp-portfolio\django-start\blog\models.py", line 8
    body = models.TextField() # attribute called body of type TextField - no max length
                                                                                      ^
IndentationError: unindent does not match any outer indentation level

(venv) C:\Users\droid\rp-portfolio\django-start>python manage.py makemigrations blog
Traceback (most recent call last):
  File "manage.py", line 18, in <module>
    execute_from_command_line(sys.argv)
  File "C:\Users\droid\django-start\venv\lib\site-packages\django\core\management\__init__.py", line 401, in execute_from_command_line
    utility.execute()
  File "C:\Users\droid\django-start\venv\lib\site-packages\django\core\management\__init__.py", line 377, in execute
    django.setup()
  File "C:\Users\droid\django-start\venv\lib\site-packages\django\__init__.py", line 24, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "C:\Users\droid\django-start\venv\lib\site-packages\django\apps\registry.py", line 114, in populate
    app_config.import_models()
  File "C:\Users\droid\django-start\venv\lib\site-packages\django\apps\config.py", line 211, in import_models
    self.models_module = import_module(models_module_name)
  File "c:\users\droid\appdata\local\programs\python\python37\lib\importlib\__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1006, in _gcd_import
  File "<frozen importlib._bootstrap>", line 983, in _find_and_load
  File "<frozen importlib._bootstrap>", line 967, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 677, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 724, in exec_module
  File "<frozen importlib._bootstrap_external>", line 860, in get_code
  File "<frozen importlib._bootstrap_external>", line 791, in source_to_code
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "C:\Users\droid\rp-portfolio\django-start\blog\models.py", line 8
    body = models.TextField() # attribute called body of type TextField - no max length
                                                                                      ^
IndentationError: unindent does not match any outer indentation level

(venv) C:\Users\droid\rp-portfolio\django-start>python manage.py migrate
Traceback (most recent call last):
  File "manage.py", line 18, in <module>
    execute_from_command_line(sys.argv)
  File "C:\Users\droid\django-start\venv\lib\site-packages\django\core\management\__init__.py", line 401, in execute_from_command_line
    utility.execute()
  File "C:\Users\droid\django-start\venv\lib\site-packages\django\core\management\__init__.py", line 377, in execute
    django.setup()
  File "C:\Users\droid\django-start\venv\lib\site-packages\django\__init__.py", line 24, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "C:\Users\droid\django-start\venv\lib\site-packages\django\apps\registry.py", line 114, in populate
    app_config.import_models()
  File "C:\Users\droid\django-start\venv\lib\site-packages\django\apps\config.py", line 211, in import_models
    self.models_module = import_module(models_module_name)
  File "c:\users\droid\appdata\local\programs\python\python37\lib\importlib\__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1006, in _gcd_import
  File "<frozen importlib._bootstrap>", line 983, in _find_and_load
  File "<frozen importlib._bootstrap>", line 967, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 677, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 724, in exec_module
  File "<frozen importlib._bootstrap_external>", line 860, in get_code
  File "<frozen importlib._bootstrap_external>", line 791, in source_to_code
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "C:\Users\droid\rp-portfolio\django-start\blog\models.py", line 8
    body = models.TextField() # attribute called body of type TextField - no max length
                                                                                      ^
IndentationError: unindent does not match any outer indentation level

(venv) C:\Users\droid\rp-portfolio\django-start>python manage.py makemigrations blog
No changes detected in app 'blog'

(venv) C:\Users\droid\rp-portfolio\django-start>python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, blog, contenttypes, projects, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying blog.0001_initial... OK
  Applying sessions.0001_initial... OK

(venv) C:\Users\droid\rp-portfolio\django-start>^A

(venv) C:\Users\droid\rp-portfolio\django-start>

Blog app: Django admin

On the other hand, you don’t want to have to write blog posts in the command line. This is where the admin comes in. It allows you to create, update, and delete instances of your model classes and provides a nice interface for doing so. Before you can access the admin, you need to add yourself as a superuser. This is why, in the previous section, you applied migrations project-wide as opposed to just for the app. Django comes with built-in user models and a user management system that will allow you to login to the admin.
Let's create a super user:
python manage.py createsuperuser
💡

Username: Gizmotronn Email: liam@acordsoftware.tech PWord: Gizmotronn1

Then run the server and go to localhost:8000/admin

Now we need to edit blog/admin.py
from django.contrib import admin
from blog.models import Post, Category, Comment # import the models you want registered on the admin page

class PostAdmin(admin.ModelAdmin): # define empty class
    pass # don’t need to add any attributes or methods to these classes. They are used to customize what is shown on the admin pages. For this tutorial, the default configuration is enough.

class CategoryAdmin(admin.ModelAdmin): 
    pass

admin.site.register(Post, PostAdmin) # Post model - register model with the admin class
admin.site.register(Category, CategoryAdmin) # Category

Create a test blog post and category

Blog app: views

Three views will need to be created in blog/views.py:

  • blog_index will display a list of posts
  • blog_detail will display a full post, the comments list and a form to add a new comment
  • blog_category will show only posts of a specific category but otherwise act like blog_index

blog_index is the simplest view to start with - just query the Post models and retrieve all its objects (like project_index)
from django.shortcuts import render
from blog.models import Post, Comment

def blog_index(request):
	posts = Post.objects.all().order_by('-created_on')
    context = {
        "posts": posts,
    }
    return render(request, "blog_index.html", context)

line 2 → import the Post & Comment modules (aka models) from the blog models

line 5 → obtain a queryset containing all posts in the database. order_by orders the Queryset according to the argument (the date the post was created) given. The - tells Django to start with the largest value.

line 6 → define the context dictionary and render the template blog_index.html

The full file (blog/views.py) can be viewed at the repo to see the context behind the blog_category view
def blog_category(request, category): # take a category name as an argument
    posts = Post.objects.filter( # query the post database for all posts that have been given the assigned category (assigned from the argument ^^)
        categories__name__contains=category # the name of the category has the category defined in the function/view definition argument/request
    ).order_by(
        '-created_on'
    )
    context = {
        "category": category,
        "posts": posts
    }
    return render(request, "blog_category.html", context)

""" blog_category view
line 20 - Django queryset filter /#/ We only want posts who have been assigned a category that is equal to the one in the argument (i.e. the one the user has clicked on) // https://docs.djangoproject.com/en/2.1/topics/db/queries/#retrieving-specific-objects-with-filters
line 25 - add these posts and category to the context dictionary
line 29 - render our template for the category - blog_category.html
We need to add a form to blog_details view for adding comments:
def blog_detail(request, pk):
    post = Post.objects.get(pk=pk)
    comments = Comment.objects.filter(post=post)
    context = {
        "post": post,
        "comments": comments,
    }

    return render(request, "blog_detail.html", context)

To add the form, create a new file in blog called forms.py
from django import forms # forms class from django module/libary

class CommentForm(forms.Form): # create a new class, displaying the comments form and inheriting from the django forms class
    author = forms.CharField( # similar to a field/model. Name of the field - author of type char field
        max_length=60, 
        widget=forms.TextInput(attrs={ # text widgets
            "class": "form-control",
            "placeholder": "Your Name"
        })
    )
    body = forms.CharField(widget=forms.Textarea(
        attrs={
            "class": "form-control", # the comment
            "placeholder": "Leave a comment!"
        })
    )

Django forms are similar to models:

Django forms are very similar to models. A form consists of a class where the class attributes are form fields. Django comes with some built-in form fields that you can use to quickly create the form you need.

We only need the author and body fields for the comment form: ^^

You’ll also notice an argument widget has been passed to both the fields. The author field has the forms.TextInput widget. This tells Django to load this field as an HTML text input element in the templates. The body field uses a forms.TextArea widget instead, so that the field is rendered as an HTML text area element. These widgets also take an argument attrs, which is a dictionary and allows us to specify some CSS classes, which will help with formatting the template for this view later. It also allows us to add some placeholder text. When a form is posted, a POST request is sent to the server. So, in the view function, we need to check if a POST request has been received. We can then create a comment from the form fields. Django comes with a handy is_valid() on its forms, so we can check that all the fields have been entered correctly.
Then add the form context to views.py
form = CommentForm()
    if request.method == 'POST':
        form = CommentForm(request.POST)
        if form.is_valid():
            comment = Comment(
                author=form.cleaned_data["author"],
                body=form.cleaned_data["body"],
                post=post
            )
            comment.save() # in the blog_details view

Finishing with

from django.shortcuts import render
from blog.models import Post, Comment
from .forms import CommentForm

def blog_index(request):
	posts = Post.objects.all().order_by('-created_on')
    context = {
        "posts": posts,
    }
    return render(request, "blog_index.html", context)

""" blog_index view
`line 2` → import the `Post` & `Comment` modules (aka models) from the blog models

`line 5` → obtain a queryset containing all posts in the database. `order_by` orders the Queryset according to the **argument** (the date the post was created) given. The `-` tells Django to start with the **largest** value. 

`line 6` → define the context dictionary and render the template `blog_index.html`
"""

def blog_category(request, category): # take a category name as an argument
    posts = Post.objects.filter( # query the post database for all posts that have been given the assigned category (assigned from the argument ^^)
        categories__name__contains=category # the name of the category has the category defined in the function/view definition argument/request
    ).order_by(
        '-created_on'
    )
    context = {
        "category": category,
        "posts": posts
    }
    return render(request, "blog_category.html", context)

""" blog_category view
line 20 - Django queryset filter /#/ We only want posts who have been assigned a category that is equal to the one in the argument (i.e. the one the user has clicked on) // https://docs.djangoproject.com/en/2.1/topics/db/queries/#retrieving-specific-objects-with-filters
line 25 - add these posts and category to the context dictionary
line 29 - render our template for the category - blog_category.html """

def blog_detail(request, pk): # take the primary key of the post in the request as an argument for the link to be displayed
    post = Post.objects.get(pk=pk) # set the value for this post to be the post with the same pk as in the argument

    form = CommentForm()
    if request.method == 'POST':
        form = CommentForm(request.POST)
        if form.is_valid():
            comment = Comment(
                author=form.cleaned_data["author"],
                body=form.cleaned_data["body"],
                post=post
            )
            comment.save()

    comments = Comment.objects.filter(post=post) # get the comments for this post
    context = { # add the post and comments to our context to be displayed and then rendered to the blog_detail.html teplate in line 44
        "post": post,
        "comments": comments,
        "form": form;
    }

    return render(request, "blog_detail.html", context) # we need to add a form to the blog post details for submitting comments - see `form`

Let's hook up the URLs now

blog/urls.py

from django.urls import path
from . import views

urlpatterns = [
    path("", views.blog_index, name="blog_index"),
    path("<int:pk>/", views.blog_detail, name="blog_detail"),
    path("<category>/", views.blog_category, name="blog_category"),
]

Then add

path("blog/", include("blog.urls")),

to personal_portfolio/urls.py

Blog app: templates