Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify deployment chapter by using pythonanywhere "autoconfigure" script #1190

Merged
merged 14 commits into from
Feb 12, 2018
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 51 additions & 106 deletions en/deploy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,32 +130,64 @@ Your code is now on GitHub. Go and check it out! You'll find it's in fine compa

# Setting up our blog on PythonAnywhere

## Sign up for a PythonAnywhere account

> **Note** You might have already created a PythonAnywhere account earlier during the install steps – if so, no need to do it again.

{% include "/deploy/signup_pythonanywhere.md" %}


## Pulling our code down on PythonAnywhere
## Creating an API token

This is something you only need to do once. When you've signed up for PythonAnywhere, you'll be taken to your dashboard. Find the link near the top right to your "Accounts" page, then select the tab named "API token", and hit the button that says "Create new API token".

<img src="images/pythonanywhere_create_api_token.png" alt="The API token tab on the Accounts page" />


When you've signed up for PythonAnywhere, you'll be taken to your dashboard or "Consoles" page. Choose the option to start a "Bash" console – that's the PythonAnywhere version of a console, just like the one on your computer.
## Configuring our site on PythonAnywhere

<img src="images/pythonanywhere_bash_console.png" alt="pointing at Other: Bash in Start a new Console" />
Go back to the main PythonAnywhere Dashboard by clicking on the logo, and choose the option to start a "Bash" console – that's the PythonAnywhere version of a command line, just like the one on your computer.

<img src="images/pythonanywhere_bash_console.png" alt="Pointing at Bash in the New Console section" />

> **Note** PythonAnywhere is based on Linux, so if you're on Windows, the console will look a little different from the one on your computer.

Let's pull down our code from GitHub and onto PythonAnywhere by creating a "clone" of our repo. Type the following into the console on PythonAnywhere (don't forget to use your GitHub username in place of `<your-github-username>`):
Deploying a web app on PythonAnywhere involves pulling down your code from GitHub, and then configuring PythonAnywhere to recognise it and start serving it as a web application. There are manual ways of doing it, but PythonAnywhere provide a helper tool that will do it all for you. Let's install it first:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/provide/provides/, as "PythonAnywhere" seems to be singular, not plural (as seen by the "is" in the note above).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep!


{% filename %}PythonAnywhere command-line{% endfilename %}
```
$ pip3.6 install --user pythonanywhere
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does --user behave when inside a virtual env? That will happen for some users.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the very first command the user runs on PythonAnywhere. There is no virtualenv active at this point.

```

That should print out some things like `Collecting pythonanywhere`, and eventually end with a line saying `Successfully installed (...) pythonanywhere- (...)`.

Now we run the helper to automatically configure our app from GitHub. Type the following into the console on PythonAnywhere (don't forget to use your GitHub username in place of `<your-github-username>`):

{% filename %}PythonAnywhere command-line{% endfilename %}
```
$ git clone https://github.com/<your-github-username>/my-first-blog.git
$ pa_autoconfigure_django.py https://github.com/<your-github-username>/my-first-blog.git
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a fresh pythonanywhere account (dasg2) with nothing done except for creating the API token, this failed for me:

22:06 ~ $ pip3.6 install --user pythonanywhere
Collecting pythonanywhere
  Downloading pythonanywhere-0.0.8.tar.gz
Requirement already satisfied: docopt in /usr/local/lib/python3.6/dist-packages (from pythonanywhere)
Requirement already satisfied: requests in /usr/local/lib/python3.6/dist-packages (from pythonanywhere)
Building wheels for collected packages: pythonanywhere
  Running setup.py bdist_wheel for pythonanywhere ... done
  Stored in directory: /home/dasg2/.cache/pip/wheels/51/a6/09/27a2fbf112b070661d9fcc722ddbb28501d822dd227d23fc4d
Successfully built pythonanywhere
Installing collected packages: pythonanywhere
Successfully installed pythonanywhere-0.0.8
22:08 ~ $ pa_autoconfigure_django.py https://github.com/das-g/my-second-blog
< Running API sanity checks >
   \
    ~<:>>>>>>>>>
< Creating web app via API >
   \
    ~<:>>>>>>>>>
Traceback (most recent call last):
  File "/home/dasg2/.local/bin/pa_autoconfigure_django.py", line 52, in <module>
    main(arguments['<git-repo-url>'], arguments['--domain'], arguments['--python'], nuke=arguments.get('--nuke'))
  File "/home/dasg2/.local/bin/pa_autoconfigure_django.py", line 33, in main
    project.create_webapp(nuke=nuke)
  File "/home/dasg2/.local/lib/python3.6/site-packages/pythonanywhere/project.py", line 28, in create_webapp
    self.webapp.create(self.python_version, self.virtualenv.path, self.project_path, nuke=nuke)
  File "/home/dasg2/.local/lib/python3.6/site-packages/pythonanywhere/api.py", line 90, in create
    raise Exception(f'PATCH to set virtualenv path via API failed, got {response}:{response.text}')
Exception: PATCH to set virtualenv path via API failed, got <Response [400]>:{"virtualenv_path":["Warning: No virtualenv detected at this path. Do you need 
to create it?"]}
22:10 ~ $

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you want to give it another go, you can either create a fresh pythonanywhere account (das3?)

👍 Worked fine with pythonanywhere-0.0.9 (with the same steps as above, on another brand-new pythonanywhere account dasg3, i.e., as instructed in the tutorial as changed by this PR)

or do a pip3.6 install --user --upgrade pythonanywhere, and then use the --nuke flag to pa_autoconfigure_django.py.

I might try that later, but as long as this option isn't mentioned in the tutorial, I don't think it's relevant to this PR.

I'll also review the rest of the changed instructions in this PR, probably some time this evening.

incidentally, i used your repo for my test, and it's just showing me the blank django "it worked" page?

Just as expected! 😃

i think by the time attendees get to the deploy chapter they do have some kind of front page / urls.py entry, right?

Nope, we have them go the "deploy early, deploy often" way.
The only thing they have by then is slightly changed project settings, one model class (for Posts) and it's registration for /admin. (And some model instances in their local DB.)

Due to first introducing some general Internet and Web concepts and then teaching the participants some basic Python, this deploy chapter is already around halfway into the tutorial. Depending on the length of the workshop, this is often as far as participants get on-site. As the note at the top of the chapter explains, deploying in this "early" state of the project should also enable the participants to more easily finish the tutorial at home on their own, should they not be able to finish it during a workshop.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great! glad it's working now. and thanks for the clarification re: the state of the repo at that point. that actually reminds me - I think the current instructions are missing the createsuperuser step. will amend them and add that later today, so you can test them this evening.

```

This will pull down a copy of your code onto PythonAnywhere. Check it out by typing `tree my-first-blog`:
As you watch that running, you'll be able to see what it's doing:

- Downloading your code from GitHub
- Creating a virtualenv on PythonAnywhere, just like the one on your own PC
- Updating your settings file with some deployment settings
- Setting up a database on PythonAnywhere using the "migrate" command
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd use inline code markup instead of quotes here, and prepend manage.py:

  • Setting up a database on PythonAnywhere using the manage.py migrate command

- Setting up your static files (we'll learn about these later)
- And configuring PythonAnywhere to serve your web app via its API

On PythonAnywhere all those steps are automated, but they're the same steps you
would have to go through with any other server provider. The main thing to notice
right now is that your database on PythonAnywhere is actually totally separate from
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 This phrasing is better than the original one, as "separate" makes the point more clearly than "different". (Judging from the questions we get on https://gitter.im/DjangoGirls/tutorial, this often lead to confusion or misconceptions.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks!

your database on your own PC -- that means it can have different posts and admin accounts.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will GitBook show this -- as an mdash (—) or ndash (–) or so, instead of a double-minus/dash? (The GitHub preview shows it as the latter.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll replace it with an explict mdash literal just in case.


Check out your code on PythonAnywhere by typing `tree`:

{% filename %}PythonAnywhere command-line{% endfilename %}
```
$ tree my-first-blog
my-first-blog/
$ tree
yourusername.pythonanywhere.com/
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is a good idea. Some might change it, some might not. In general things that can't be copy-pasted and don't add actual learning value should be avoided.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed. I was just adapting from the existing tree listing. Options:

  • remove the tree command altogether
  • tree *.pythonanywhere.com should pick up on any username
  • just tree with no arguments will probably be fine! since the blog repo will be the only folder in their account, that's all they would see, apart from our Readme.txt

that last seems like the easiest? I'll make that change unless y'all have another preference.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, i see i had already implemented solution 3? so there is actually no copy-paste error possible here...

├── blog
│ ├── __init__.py
│ ├── admin.py
Expand All @@ -173,116 +205,28 @@ my-first-blog/
└── wsgi.py
```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The output of tree is now much larger than shown in the tutorial:

18 directories, 84 files (click to expand)

.
├── README.txt
└── dasg3.pythonanywhere.com
    ├── blog
    │   ├── __init__.py
    │   ├── __pycache__
    │   │   ├── __init__.cpython-36.pyc
    │   │   ├── admin.cpython-36.pyc
    │   │   └── models.cpython-36.pyc
    │   ├── admin.py
    │   ├── apps.py
    │   ├── migrations
    │   │   ├── 0001_initial.py
    │   │   ├── __init__.py
    │   │   └── __pycache__
    │   │       ├── 0001_initial.cpython-36.pyc
    │   │       └── __init__.cpython-36.pyc
    │   ├── models.py
    │   ├── tests.py
    │   └── views.py
    ├── db.sqlite3
    ├── manage.py
    ├── mysite
    │   ├── __init__.py
    │   ├── __pycache__
    │   │   ├── __init__.cpython-36.pyc
    │   │   ├── settings.cpython-36.pyc
    │   │   └── urls.cpython-36.pyc
    │   ├── settings.py
    │   ├── urls.py
    │   └── wsgi.py
    └── static
        └── admin
            ├── css
            │   ├── base.css
            │   ├── changelists.css
            │   ├── dashboard.css
            │   ├── fonts.css
            │   ├── forms.css
            │   ├── login.css
            │   ├── rtl.css
            │   └── widgets.css
            ├── fonts
            │   ├── LICENSE.txt
            │   ├── README.txt
            │   ├── Roboto-Bold-webfont.woff
            │   ├── Roboto-Light-webfont.woff
            │   └── Roboto-Regular-webfont.woff
            ├── img
            │   ├── LICENSE
            │   ├── README.txt
            │   ├── calendar-icons.svg
            │   ├── gis
            │   │   ├── move_vertex_off.svg
            │   │   └── move_vertex_on.svg
            │   ├── icon-addlink.svg
            │   ├── icon-alert.svg
            │   ├── icon-calendar.svg
            │   ├── icon-changelink.svg
            │   ├── icon-clock.svg
            │   ├── icon-deletelink.svg
            │   ├── icon-no.svg
            │   ├── icon-unknown-alt.svg
            │   ├── icon-unknown.svg
            │   ├── icon-yes.svg
            │   ├── inline-delete.svg
            │   ├── search.svg
            │   ├── selector-icons.svg
            │   ├── sorting-icons.svg
            │   ├── tooltag-add.svg
            │   └── tooltag-arrowright.svg
            └── js
                ├── SelectBox.js
                ├── SelectFilter2.js
                ├── actions.js
                ├── actions.min.js
                ├── admin
                │   ├── DateTimeShortcuts.js
                │   └── RelatedObjectLookups.js
                ├── calendar.js
                ├── cancel.js
                ├── change_form.js
                ├── collapse.js
                ├── collapse.min.js
                ├── core.js
                ├── inlines.js
                ├── inlines.min.js
                ├── jquery.init.js
                ├── popup_response.js
                ├── prepopulate.js
                ├── prepopulate.min.js
                ├── prepopulate_init.js
                ├── timeparse.js
                ├── urlify.js
                └── vendor
                    ├── jquery
                    │   ├── LICENSE-JQUERY.txt
                    │   ├── jquery.js
                    │   └── jquery.min.js
                    └── xregexp
                        ├── LICENSE-XREGEXP.txt
                        ├── xregexp.js
                        └── xregexp.min.js

18 directories, 84 files


Maybe we should ignore some stuff with -I, e.g. tree -I 'static|__pycache__'? Though that would need some explanation, too.

Copy link
Contributor Author

@hjwp hjwp Jan 24, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well spotted. tree -L 3 also produces a reasonably sane amount of output.

tree -L 3 -- 22 directories, 93 files
$ tree -L 3
.
├── blog
│   ├── __init__.py
│   ├── __pycache__
│   │   ├── __init__.cpython-36.pyc
│   │   ├── [...]
│   ├── admin.py
│   ├── forms.py
│   ├── migrations
│   │   ├── 0001_initial.py
│   │   ├── __init__.py
│   │   └── __pycache__
│   ├── models.py
│   ├── static
│   │   └── css
│   ├── templates
│   │   └── blog
│   ├── tests.py
│   ├── urls.py
│   └── views.py
├── db.sqlite3
├── manage.py
├── mysite
│   ├── __init__.py
│   ├── __pycache__
│   │   ├── __init__.cpython-36.pyc
│   │   ├── [...]
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── static
    ├── admin
    │   ├── css
    │   ├── fonts
    │   ├── img
    │   └── js
    └── css
        └── blog.css

I'm not sure we really want to dive into an explanation of tree arguments though? How about just using ls:

$ ls
blog  db.sqlite3  manage.py  mysite  static
$ ls blog/
__init__.py  __pycache__  admin.py  forms.py  migrations  models.py  static
templates  tests.py  urls.py  views.py

that seems simpler, maybe just go with that for now?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, good call. 👍

ls has been introduced (at least to UNIXoid users) in Introduction to the command-line interface, so let's stick to that.


You can also go to the "Files" tab and navigate to your code if you want to.

### Creating a virtualenv on PythonAnywhere

Just like you did on your own computer, you can create a virtualenv on PythonAnywhere. In the Bash console, type:

{% filename %}PythonAnywhere command-line{% endfilename %}
```
$ cd my-first-blog

$ virtualenv --python=python3.6 myvenv
Running virtualenv with interpreter /usr/bin/python3.6
[...]
Installing setuptools, pip...done.

$ source myvenv/bin/activate

(myvenv) $ pip install django~=1.11.0
Collecting django
[...]
Successfully installed django-1.11.3
```


> **Note** The `pip install` step can take a couple of minutes. Patience, patience! But if it takes more than five minutes, something is wrong. Ask your coach.

<!--TODO: think about using requirements.txt instead of pip install.-->

### Creating the database on PythonAnywhere

Here's another thing that's different between your own computer and the server: it uses a different database. So the user accounts and posts can be different on the server and on your computer.

Just as we did on your own computer, we repeat the step to initialize the database on the server, with `migrate` and `createsuperuser`:

{% filename %}PythonAnywhere command-line{% endfilename %}
```
(mvenv) $ python manage.py migrate
Operations to perform:
[...]
Applying sessions.0001_initial... OK
(mvenv) $ python manage.py createsuperuser
```

## Publishing our blog as a web app

Now our code is on PythonAnywhere, our virtualenv is ready, and the database is initialized. We're ready to publish it as a web app!

Click back to the PythonAnywhere dashboard by clicking on its logo, and then click on the **Web** tab. Finally, hit **Add a new web app**.

After confirming your domain name, choose **manual configuration** (N.B. – *not* the "Django" option) in the dialog. Next choose **Python 3.6**, and click Next to finish the wizard.

> **Note** Make sure you choose the "Manual configuration" option, not the "Django" one. We're too cool for the default PythonAnywhere Django setup. ;-)


### Setting the virtualenv

You'll be taken to the PythonAnywhere config screen for your webapp, which is where you'll need to go whenever you want to make changes to the app on the server.

<img src="images/pythonanywhere_web_tab_virtualenv.png" />

In the "Virtualenv" section, click the red text that says "Enter the path to a virtualenv", and enter `/home/<your-PythonAnywhere-username>/my-first-blog/myvenv/`. Click the blue box with the checkmark to save the path before moving on.

> **Note** Substitute your own PythonAnywhere username as appropriate. If you make a mistake, PythonAnywhere will show you a little warning.


### Configuring the WSGI file

Django works using the "WSGI protocol", a standard for serving websites using Python, which PythonAnywhere supports. The way we configure PythonAnywhere to recognize our Django blog is by editing a WSGI configuration file.

Click on the "WSGI configuration file" link (in the "Code" section near the top of the page – it'll be named something like `/var/www/<your-PythonAnywhere-username>_pythonanywhere_com_wsgi.py`), and you'll be taken to an editor.

Delete all the contents and replace them with the following:

{% filename %}&lt;your-username&gt;_pythonanywhere_com_wsgi.py{% endfilename %}
```python
import os
import sys

path = os.path.expanduser('~/my-first-blog')
if path not in sys.path:
sys.path.append(path)

os.environ['DJANGO_SETTINGS_MODULE'] = 'mysite.settings'

from django.core.wsgi import get_wsgi_application
from django.contrib.staticfiles.handlers import StaticFilesHandler
application = StaticFilesHandler(get_wsgi_application())
```

This file's job is to tell PythonAnywhere where our web app lives and what the Django settings file's name is.

The `StaticFilesHandler` is for dealing with our CSS. This is taken care of automatically for you during local development by the `runserver` command. We'll find out a bit more about static files later in the tutorial, when we edit the CSS for our site.

Hit **Save** and then go back to the **Web** tab.
## You are now live!

We're all done! Hit the big green **Reload** button and you'll be able to go view your application. You'll find a link to it at the top of the page.
Your site should now be live on the public Internet! Click through to the
PythonAnywhere "Web" tab to get a link to it. You can share this with anyone you want :)


## Debugging tips

If you see an error when you try to visit your site, the first place to look for some debugging info is in your **error log**. You'll find a link to this on the PythonAnywhere [Web tab](https://www.pythonanywhere.com/web_app_setup/). See if there are any error messages in there; the most recent ones are at the bottom. Common problems include:

- Forgetting one of the steps we did in the console: creating the virtualenv, activating it, installing Django into it, migrating the database.
If you see an error while running the `pa_autoconfigure_django.py` script, there are
a couple of common causes:

- Making a mistake in the virtualenv path on the Web tab – there will usually be a little red error message on there, if there is a problem.
- Forgetting to create your API token.
- Making a mistake in your GitHub URL

- Making a mistake in the WSGI configuration file – did you get the path to your my-first-blog folder right?

- Did you pick the same version of Python for your virtualenv as you did for your web app? Both should be 3.6.
If you see an error when you try to visit your site, the first place to look for some debugging info is in your **error log**. You'll find a link to this on the PythonAnywhere [Web tab](https://www.pythonanywhere.com/web_app_setup/). See if there are any error messages in there; the most recent ones are at the bottom.

There are also some [general debugging tips on the PythonAnywhere wiki](https://www.pythonanywhere.com/wiki/DebuggingImportError).
There are also some [general debugging tips on the PythonAnywhere help site](http://help.pythonanywhere.com/pages/DebuggingImportError).

And remember, your coach is here to help!

Expand All @@ -295,3 +239,4 @@ Once you have a few posts created, you can go back to your local setup (not Pyth


Give yourself a *HUGE* pat on the back! Server deployments are one of the trickiest parts of web development and it often takes people several days before they get them working. But you've got your site live, on the real Internet, just like that!

Binary file modified en/deploy/images/pythonanywhere_bash_console.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
1 change: 1 addition & 0 deletions en/deploy/signup_pythonanywhere.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ Next, it's time to sign up for a free "Beginner" account on PythonAnywhere.
* [www.pythonanywhere.com](https://www.pythonanywhere.com/)



Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the added blank line?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no idea. have removed it again.

> **Note** When choosing your username here, bear in mind that your blog's URL will take the form `yourusername.pythonanywhere.com`, so choose either your own nickname or a name for what your blog is all about.
2 changes: 1 addition & 1 deletion en/django_forms/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ $ git push

{% filename %}command-line{% endfilename %}
```
$ cd my-first-blog
$ cd ~/$USER.pythonanywhere.com
$ git pull
[...]
```
Expand Down
25 changes: 21 additions & 4 deletions en/extend_your_application/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,10 @@ OK, we can refresh our page and see if `TemplateDoesNotExist` is gone now.

Yay! It works!

## One more thing: deploy time!

It'd be good to see if your website will still be working on PythonAnywhere, right? Let's try deploying again.
# Deploy time!

It'd be good to see if your website still works on PythonAnywhere, right? Let's try deploying again.

{% filename %}command-line{% endfilename %}
```
Expand All @@ -180,11 +181,27 @@ Then, in a [PythonAnywhere Bash console](https://www.pythonanywhere.com/consoles

{% filename %}command-line{% endfilename %}
```
$ cd my-first-blog
$ cd ~/$USER.pythonanywhere.com
$ git pull
[...]
```

Finally, hop on over to the [Web tab](https://www.pythonanywhere.com/web_app_setup/) and hit **Reload**.

## Updating the static files on the server

Servers like PythonAnywhere like to treat "static files" (like CSS files) differently from Python files, because they can optimise for them to be loaded faster. As a result, whenever we make changes to our CSS files, we need to run an extra command on the server to tell it to update them. The command is called `collectstatic`.

Start by activating your virtualenv if it's not still active from earlier:

{% filename %}command-line{% endfilename %}
```
$ workon $USER.pythonanywhere.com
(...)$ python manage.py collectstatic
[...]
```

The `manage.py collectstatic` command is a bit like `manage.py migrate`. We make some changes to our code, and then we tell Django to _apply_ those changes, either to the server's collection of static files, or to the database.

In any case, we're now ready to hop on over to the [Web tab](https://www.pythonanywhere.com/web_app_setup/) and hit **Reload**.

And that should be it! Congrats :)
2 changes: 1 addition & 1 deletion en/html/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ $ git push

{% filename %}command-line{% endfilename %}
```
$ cd ~/my-first-blog
$ cd ~/$USER.pythonanywhere.com
$ git pull
[...]
```
Expand Down