Note: This site is currently "Under construction". I'm migrating to a new version of my site building software. Lots of things are in a state of disrepair as a result (for example, footnote links aren't working). It's all part of the process of building in public. Most things should still be readable though.

Django 3 Tutorail - Part 1

These notes are currently in the grimoire becuase there's a lot of work to do to get them to not blow up MDX

Code

-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
--

TODO: Put this in for handling IDs that don't exist (verify this is the good way to do it)

from django.http import Http404


def detail_path(request, pk):
    try:
        obj = Detail.objects.get(pk=pk)
    except Detail.DoewNotExist:
        raise Http404
    return(...)





TODO: Think about using straight calls in the urls to `views.whatever` instead of includes? 


-- hr


More advanced, but look at replacing the ID with a UUID but putting this in your model class:

    db_id = models.UUIDField(primary_key=True, default=uuid.uuid4())
    not something taht should be necessary most of the time, but it might have use



-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
--

Depending on how new you are to Python and/or Programming, you're not going to understand parts of this. That's fine for now. 

You're going to see a lot of errors in your programming career. They're your friends. Sometimes they piss you off, but they always have your back. 


Here's the thing to keep in mind, error messages are your friend. We're going to use them. They'll tell us what we need to do. 

Each time we see a new one, we're getting more information about what to do and how to make things work. Every time we see a new one, we're making progress. 

We're doing to ignore git for now. 

Your going to want to have four things opened:

- 2 terminal windows (one we'll call the "server terminal" and the other we'll call "TKTKTKT")
- your text editor
- your browser


V2:


- Make the directory for your project and move into it:

        mkdir django

    And move into it:

        cd django

- Make sure you have a virtual environment setup:

        TODO: Replace this wity pyvenv
        --python3 -m venv venv--

    Then make sure you're using it with:

        source venv/bin/activate

    You'll see `(venv)` show up in front of your command line prompt. 

    TODO: Put note in here about opening second terminal window and also start up venv for it. 


- Install Django:

        pip install django

    You'll probably see a warning about the version of pip your using. That looks something like the below. You can safely ignore it:

        WARNING: You are using pip version 19.2.3, however version 20.2.3 is available.
        You should consider upgrading via the 'pip install --upgrade pip' command.


- Make a new Django site:

        django-admin startproject config .

    If you see an error like this: CommandError: 'tutorial-site-2020-10-17' is not a valid project name. Please make sure the name is a valid identifier. It's because you used dashes instead of underscores.

- Move into your site directory with this. The rest of these notes will assume you're there as you site root

        TKTKTK, figre out what you need to do with the directories ehre. 

    It's important to point out there there's another directory called `tutorial_site` inside the one you just made. It's just part of the way Django is structured, but it can be confusing. 

- We can check the server right now. 

    Be ready, you're about to see your first (intentional) warning/error. 

    It's going to throw a big red warning starting with something like "You have 18 unapplied migration(s)", but that's okay for now. Just run this:

        python manage.py runserver

    And then checkout the site running on your local machine with:

        <<link|http://127.0.0.1:8000|http://127.0.0.1:8000>>

    Kill it with CTRL+C

    Go back to the web browser and you'll see that the site is offline. (You'll probably run into this again, when you aren't expecting it. This is just so you know what it looks like, when the server is either stopped or couldn't start.)


- Now, run this command. 

        python manage.py migrate

    Don't worry to much about it at this point, but basically when you start a new Django project it creates a database. It's not fully setup though. This command does that setup. We'll also use it when we make changes, but running it now just prevents a bunch of scary warning messages from showing up. 


- Now, just to prove it worked, run the server again. This time you won't see the big red warning....


    python manage.py runserver



- When you've finished admiring your shinny new site, stop the server by holding down the "Control" key and pressing "C". 

That's it, your live with the platform to start building on.


-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
--

### Making A Home Page


TKTKTKTKKTT: Consider building the homepage directly at the root instead of `/pages/homepage` and then moving it. 



We're going to build the page at:

    TKTKTKTK: Don't build the page then move it, 
    # Build it and the home page level, you can 
    # explain more about the paths in later. 

    http://127.0.0.1:8000/pages/homepage

The move it to:

    http://127.0.0.1:8000/

So that it it's the first thing you see. You know, because it's a home page. 




All the wires are hooked up. So, let's make our own home page. We're going to do this in a way that causes lots of errors and then address each error as we see it. The other way is to make a whole bunch of changes without seeing anything but that can get very confusing when you finally get to the end if something doesn't work right. 

THe way we'll do this is by making a "pages" module (aka app) and putting the home page in it. 

There are, of course, other ways to appraoch this, but this will give you good experience with error message... TKTKTKTK doing it this way give you experience with error messages that will also show you how to move forward. 


For now, Just follow the directions to get the page running and then we'll go back and walk through the details. 


- First, start up the server if it's not already running:

        python manage.py runserver

    Make sure it's live:

        http://127.0.0.1:8000



- Our first step is to setup the paths to point the root of our home page to the path where we're going to put the file we want to display. 

    We do that by updating this file

        /Users/alans/django/tutorial_site/tutorial_site/urls.py

    (And just to point it out, `tutorial_site/tutorial_site` is not a typo. You gotta go into the second one, as confusing as that is. )

    Change this:

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

    To this:

        urlpatterns = [
            path('pages/', include('pages.urls')),
            path('admin/', admin.site.urls),
        ]



    The command line will freak out and throw an error that ends with something like this:

        File "/Users/alans/django/tutorial_site/tutorial_site/urls.py", line 21, in <module>
            path('', include('pages.urls'))
        NameError: name 'include' is not defined

    (NOTE Also that the website shows you the same error that you got at the start before we setup the server. That's because the server can't start in this state. )

    Note that the file listed "ROOT_DIR/tutorail_site/urls.py" is the one we just worked on. (In my case ROOT_DIR = "/Users/alans/django") So, we know that's were the problem is. To fix this error, we need to change this:


        from django.urls import path

    To this:

        from django.urls import include, path


Next error:


    ModuleNotFoundError: No module named 'pages'


Fix:

    Stop the web server with:

        CTRL+C

    Then run

        python manage.py startapp pages


    Note that this made a new "pages" directory at "DJANGO_ROOT/tutorail_site/pages". There's also other stuff in there. We'll get to it later, but you can sniff around if you're interested. 

     And in `~/django/tutorial_site/settings.py` add this line into `INSTALLED_APPS`:

            'pages',

    So, it'll look like this:

        INSTALLED_APPS = [
            'django.contrib.admin',
            'django.contrib.auth',
            'django.contrib.contenttypes',
            'django.contrib.sessions',
            'django.contrib.messages',
            'django.contrib.staticfiles',
            'pages',
        ]

 
Error:

    Run the server again:
        
        python manage.py runserver

    And you'll see our new error: 

        ModuleNotFoundError: No module named 'pages.urls'


Fix:

    Make this file:

        touch /Users/alans/django/tutorial_site/pages/urls.py

    Put this in that file (Don't worry too much about it right now):

            from django.urls import path

            from . import views

            urlpatterns = [
                path('homepage', views.homepage),
            ]


    TKTKTKTKT: Put in note about pathing. Explain bascially what you did at a high level. 


    TKTKTKT: consider making actual URLs to start with. (Don't know what I meant by that.... )

Error:

    Stop the server if it's running:

        [Command] + c

    Then, run it again:

        python manage.py runserver


    And we made progress and got ourselves a new error message:

        AttributeError: module 'pages.views' has no attribute 'homepage'


Fix:

    Edit: ~/django/tutorial_site/pages/views.py to add this. 

            def homepage(request):
                return render(
                    request, 
                    'pages/homepage.html'
                )

    So, the full file will look like:


        from django.shortcuts import render

        def homepage(request):
            return render(
                request, 
                'pages/homepage.html'
            )



    The server will start working with the last messages looking like:

        Django version 3.1.2, using settings 'tutorial_site.settings'
        Starting development server at http://127.0.0.1:8000/
        Quit the server with CONTROL-C.    


Error:

    With the command line server running we can switch to the Browser and try to open:

        http://127.0.0.1:8000/pages/homepage


    The error you'll see is:

        TemplateDoesNotExist at /pages/homepage
        pages/homepage.html


Fix:

    Stop the server since we'll need to make two directories to fix this:

        Command+c

    We need to make two directories and a file to fix this. The first directory is:

        mkdir /Users/alans/django/tutorial_site/pages/templates
        mkdir /Users/alans/django/tutorial_site/pages/templates/pages


    (yeah, that `pages/tempates/pages` is a little weird looking, but that's the just how django works)

    Make a new file in that last director called:

        touch /Users/alans/django/tutorial_site/pages/templates/pages/homepage.html


    With those directories made, we can put make a new HTML file with:

        <html>
        <body>
        <h3>Hello, World</h3>
        </body>
        </html>

    And save it to:

        /Users/alans/django/tutorail_site/pages/templates/pages/homepage.html




    Restart the server and refresh the web page and you'll see your page. 

        python manage.py runserver

    And, we're live at:

        http://127.0.0.1:8000/pages/homepage

    The last thing we need to do is to move it from:

        http://127.0.0.1:8000/pages/homepage

    To:

        http://127.0.0.1:8000/

    This is done by updating the urls.py file. 

    TKTKTKTKT: Add more description for what's going on here.

    First one is:

        /Users/alans/django/tutorial_site/tutorail_site/urls.py

    Change this:

        path('pages/', include('pages.urls')),

    To this:

        path('', include('pages.urls')),

    This will fail

        http://127.0.0.1:8000/pages/homepage

    Because the server is now setup to server it here:
    
        http://127.0.0.1:8000/homepage

    TKTKTKT: put in notes on how URLs work here. 

    Now, we move it all the way up, 

    That's done by editing `/Users/alans/django/tutorial_site/pages/urls.py`, and changing this:

        path('homepage', views.homepage),

    to this:

        path('', views.homepage),


    And, that's it! Our home page is now live at:

        http://127.0.0.1:8000

    Boom.




### Make an About Page


TODO: Figure out if the slash at the end (e.g. `about/` is best practice and if there's problems likely if you don't do it and just do `about` without the slash)


Lots of the work to build the home page supports other pages. To add an About page, all we need to do is:

-- hr


Let's start out by making a link to the about page that we can work with. 

Add this line to:

    <p><a href="/about">About Page</a>

To:

    /Users/alans/tutorial_site/pages/templates/pages/homepage.html

So that the file looks like this:


    
        <html>
        <body>
        <h3>Hello, World</h3>
        <p><a href="/about">About Page</a>
        </body>
        </html>

        
Now when we click on that link, we'll get a 404 (Page not found)

    http://127.0.0.1:8000/about

You'll get a 404. 

In this file:

    /Users/alans/django/tutorial_site/pages/urls.py

From:
urlpatterns = [
    path('', views.homepage),
]

To:
urlpatterns = [
    path('about', views.about),
    path('', views.homepage),
]    


Error:

    Will render an error in the Server Terminal

        AttributeError: module 'pages.views' has no attribute 'about'


Fix:
    
    Add this to ~/django/tutorail_site/pages/views.py:

        def about(request):
            return render(
                request, 
                'pages/about.html'
            )

    So the full file looks like:

        from django.shortcuts import render

        def homepage(request):
            return render(
                request, 
                'pages/homepage.html'
            )

        def about(request):
            return render(
                request, 
                'pages/about.html'
            )        

    NOTE: If you get `IndentationError: unexpected indent` it means the defs are nested incorrectly. 



Error:

    The Server Terminal will be okay now and the site will be serving. 

    Go here to see our next error:

        http://127.0.0.1:8000/about

    Which is:

        TemplateDoesNotExist at /about/
        pages/about.html

Fix:

    Make this file:

        touch /Users/alans/django/tutorial_site/pages/templates/pages/about.html


    Put this HTML:

        <html>
        <body>
        <h3>About Page</h3>
        </body>
        </html>

 
    That gets it to show up. And now we can add this line, to link back to the home page:


    <p>
        <a href="/">Home Page</a>
        <a href="/about/">About Page</>
    </p>

That's it.



-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
--

# Linking our pages:

Our links are currently hard coded. But we want to use Django to create them. 

TODO: Switch this to make homepage first then about page and 



        TODO: This needs clean up


Making it dynamic:

    Add this to pages/urls.py

        app_name = 'pages'

        TKTKTL: Figure out how to describe what `app_name='pages'` does. Pretty sure that lets the URL handlers, know that the first part of "pages.about" is where "pages" = "app_name".

    And the update:

        path('about', views.about),

    To this:

        path('about', views.about, name='about'),


    So the full file looks like:

        from django.urls import path

        from . import views

        app_name = 'pages'

        urlpatterns = [
            path('', views.homepage),
            path('about', views.about, name='about'),
        ]      

    That gives us the ability to use `pages:about` (where pages is the `app_name` and `about` is the name in the `path()` call. It looks like this:

        <p>
            <a href="/about/">About Page</a>
            <a href="{% url 'pages:about' %}">About Page</a>
        </p>

    To get our home page do this, change this:

        path('', views.homepage),

    To this:

        path('', views.homepage, name='homepage'),
    
    We don't need to add `app_name` because we already have. 



    Full pages:


        <html>
        <body>
        <h3>Homepage - Hello, World</h3>
        <p>
            <a href="{% url 'pages:homepage' %}">Home Page</a>
            <a href="{% url 'pages:about' %}">About Page</a>
        </p>
        </body>
        </html>



        <html>
        <body>
        <h3>About Page</h3>
        <p>
            <a href="{% url 'pages:homepage' %}">Home Page</a>
            <a href="{% url 'pages:about' %}">About Page</a>
        </p>
        </body>
        </html>





-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
--

### Headers and Footers


Now we're going to add consistent headers/footers to the pages. 


To start, we're going to introduce an intentional error. Change ~/homepage.html to:

    TODO: Make sure this is the right way to call top level templates. Of if maybe you should put them in their own app?


        {% extends 'base.html' %}

        <h3>Homepage</h3>
        <p>
            <a href="{% url 'pages:homepage' %}">Home Page</a>
            <a href="{% url 'pages:about' %}">About Page</a>
        </p>

Error:

        TemplateDoesNotExist at /
        base.html

    You'll also see this in the terminal:

        django.template.exceptions.TemplateDoesNotExist: base.html
        [18/Oct/2020 15:49:39] "GET / HTTP/1.1" 500 118815




Fix:

    Create dir:

    mkdir /Users/alans/django/tutorial_site/templates

    then add:
    

        touch /Users/alans/django/tutorial_site/templates/base.html

        NOTE That you're not in:

            django/tutorial_site/tutorial_site/templates
        But:
            django/tutorial_site/templates


    In 

        /Users/alans/django/tutorail_site/tutorail_site/settings.py:

    In the section:

        TEMPLATES = [...]

    Update:

        'DIRS': [],
    To:
        
        'DIRS': [str(BASE_DIR.joinpath('templates'))],



    TKTKTKTKT: Change 'body_block' to 'content', maybe?

    with:

        <!DOCTYPE html>

        <html>
            <body>
            <h1>Tutorial Site</h1>
                <p>
                <a href="{% url 'pages:homepage' %}">Home Page</a>
                <a href="{% url 'pages:about' %}">About Page</a>
                </p>
                {% block body_block %}{% endblock %}
            </body>
        </html>    


    The "Home Page" text will disappear and be replace by "Tutorail Site". That's because base.html is being called, but we aren't populating it. 

    TKTKTKT: Talk about global nav on all our pages. 

Fix:

    Add these to homepage.html so that it looks like this:

        {% extends 'base.html' %}

        {% block body_block %}
        <h3>Homepage - Hello, World</h3>
        {% endblock %}

        And we can do the same thing to about.html


        {% extends 'base.html' %}

        {% block body_block %}
        <h3>About Page</h3>
        {% endblock %}


    TKKTKTKTK: Add basic CSS stuff. 



-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
--

First make a page to hold your data. 

Attempt 2

- Add link to checklist on the template in the base.html file

    <!DOCTYPE html>

    <html>
        <body>
        <h1>Tutorial Site</h1>
            <p>
                <a href="{% url 'pages:homepage' %}">Home Page</a>
                <a href="{% url 'pages:about' %}">About Page</a>
                <a href="/checklist/">Checklist</a>
            </p>

            {% block body_block %}{% endblock %}
        </body>
    </html>   


Error:

    404


Add this:

            path('checklist/', include('checklist.urls')),    

        To:

            /Users/alans/django/tutorial_site/tutorial_site/urls.py

        So you get:

            path('admin/', admin.site.urls),
            path('checklist/', include('checklist.urls')),
            path('', include('pages.urls')),



Error:

    ModuleNotFoundError: No module named 'checklist'

Fix:

    Stop Server:
    Run:
        python manage.py startapp checklist

    Run server:
        python manage.py runserver


Error:

    ModuleNotFoundError: No module named 'checklist.urls'

Fix:

    Gotta do two things to fix that, first:

        'checklist.apps.ChecklistConfig', 

    in INSTALLED_APPS

    Then, 

        touch /Users/alans/django/tutorial_site/checklist/urls.py

Error:

    Run the server and you'll get this giant error:

        django.core.exceptions.ImproperlyConfigured: The included URLconf '<module 'checklist.urls' from '/Users/alans/django/tutorial_site/checklist/urls.py'>' does not appear to have any patterns in it. If you see valid patterns in the file then the issue is probably caused by a circular import.

Fix:

    Put this inside:

        /Users/alans/django/tutorial_site/checklist/urls.py

    Content:

        from django.urls import path

        from . import views

        app_name = 'checklist'

        urlpatterns = [
            path('', views.checklist_homepage, name="checklist_homepage"),
        ]

    TKTKTKT: Write up notes about how we're adding `app_name` and `name=` here at the start compared to last time when we added them after seeing errors. This is because we'll pretty much always need them. 

Error:

    AttributeError: module 'checklist.views' has no attribute 'checklist_homepage'

Fix:

    Edit: /Users/alans/django/tutorial_site/checklist/views.py to add this. 

            def checklist_homepage(request):
                return render(
                    request, 
                    'checklist/checklist_homepage.html'
                )


    So, the full file will look like:


        from django.shortcuts import render

        def homepage(request):
            return render(
                request, 
                'pages/homepage.html'
            )


Error:

        TemplateDoesNotExist at /checklist/
        checklist/checklist_homepage.html


Fix:

    Make directories:

        mkdir /Users/alans/django/tutorial_site/checklist/templates
        mkdir /Users/alans/django/tutorial_site/checklist/templates/checklist

    Make a new file with:

        touch /Users/alans/django/tutorial_site/checklist/templates/checklist/checklist_homepage.html

    Run the server:

        python manage runserver

    Our page loads, but there's nothing in it since `checklist_homepage.html` is blank. So, add this to it:

        {% extends 'base.html' %}

        {% block body_block %}
        <h3>Checklist Page</h3>
        {% endblock %}

    That has it working . 

    Last thing we need to do, is update the link in the `base.html` template.

    From:

        <a href="/checklist">Checklist</a>

    To:
        <a href="{% url 'checklist:checklist_homepage' %}">Checklist</a>


Now we've got something we can work with. 



-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
--

Next stop is the admin page:

First, make the super user:

Working with Forms and the admin:

Create the super user:

        python manage.py createsuperuser

    Choose whatever name you want and give it a password (you don't have to put in an email address)


-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
--



http://127.0.0.1:8000/admin/

Let's add a link to our header (Django provides this out of the box)

        <a href="{% url 'admin:index' %}">Admin</a>

    So we get this:

        <!DOCTYPE html>

        <html>
            <body>
            <h1>Tutorial Site</h1>
                <p>
                    <a href="{% url 'pages:homepage' %}">Home Page</a>
                    <a href="{% url 'pages:about' %}">About Page</a>
                    <a href="{% url 'checklist:checklist_homepage' %}">Checklist</a>
                    <a href="{% url 'admin:index' %}">Admin</a>
                </p>

                {% block body_block %}{% endblock %}
            </body>
        </html>       

Click the link and we'll hit our admin page. 

There are a couple things there for users and groups. We don't need to mess with them right now, but click into them if you'd like. 

In order to get our checklist showing up, we need to make our checklist stuff. 


We'll need to do three things:

    - Create our model definition
    - Run migrations to update the database with the model definition
    - Add the app to the admin dashboard



- Add this to this file:

        /Users/alans/django/tutorial_site/checklist/models.py

    Code: 

        class ChecklistItem(models.Model):
            checklist_text = models.CharField(
                "Checklist Item",
                max_length=200
            )


    The full file will look like this:

        from django.db import models

        class ChecklistItem(models.Model):
            checklist_text = models.CharField(
                "Checklist Item",
                max_length=200
            )


    Do the migrations with:

        python manage.py makemigrations checklist
        python manage.py migrate

    Add it to the admin dashboard by updating the apps `admin.py` file. In this case, that's

        /Users/alans/checklist/admin.py

    Add these two lines:


        from .models import ChecklistItem

        admin.site.register(ChecklistItem)

    So the full file looks like:

        from django.contrib import admin

        from .models import ChecklistItem

        admin.site.register(ChecklistItem)

    Now, refresh the page and you can add and remove checklist items as you see fit. 

    Note that when they are listed, it just says `ChecklistItem object (2)`. We'll fix that soon. 

    Make sure to add a few to play with. 

    Note that when you see the list, it'll say `ChecklistItem object (1)`, `ChecklistItem object (2)`, etc... To see the actual text, we can add a `__str__ method to your class. 

    Add this to `example/checklist/models/py`:

        def __str__(self):
            return self.checklist_text

    So that the full file looks like this:

        from django.db import models

        class ChecklistItem(models.Model):
            checklist_text = models.CharField(
                "Checklist Item", 
                max_length=200
            )
            def __str__(self):
                return self.checklist_text

Make sure to indent the new `def __str__` so that it's at the same level as `checklist_text`.

That's it for this section. 

-- hr


We're going to add a debug thing for this tutorial only. Like a lot of debug stuff, you don't want to run with this in production. In fact, you don't want to run with with in general development. We're only using it here because it helps show how:

        In /Users/alans/django/tutorial_site/tutorial_site/settings.py

    Add this:

        'string_if_invalid': 'INVALID_CALL',

    Inside the `OPTIONS` section of `TEMPLATES`. 

    It should look like this:


        TEMPLATES = [
            {
                'BACKEND': 'django.template.backends.django.DjangoTemplates',
                'DIRS': [str(BASE_DIR.joinpath('templates'))],
                'APP_DIRS': True,
                'OPTIONS': {
                    'string_if_invalid': 'INVALID_CALL',
                    'context_processors': [
                        'django.template.context_processors.debug',
                        'django.template.context_processors.request',
                        'django.contrib.auth.context_processors.auth',
                        'django.contrib.messages.context_processors.messages',
                    ],
                },
            },
        ]



Getting stuff to show up on the checklist_homepage

We'll do that piece by piece. 

Start on our checklist page:

        http://127.0.0.1:8000/checklist/

The way data gets to the template is by passing a "context" variable. 

We'll work on these two files:

        /Users/alans/django/tutorial_site/checklist/templates/checklist/checklist_homepage.html
        /Users/alans/django/tutorial_site/checklist/views.py

We'll start with the template where we request a variable named `key`. 

    So, setup:

        /Users/alans/django/tutorial_site/checklist/templates/checklist/checklist_homepage.html

    With this:

        {% extends 'base.html' %}

        {% block body_block %}
        <h3>Checklist Page</h3>

        <p>
            When we request "item" we get: {{ item }}
        </p>
        {% endblock %}


Look at the page, and you'll see:

    When we request "item" we get: INVALID_CALL

That's because nothing is getting passed to the template. We can fix that by passing a dict by adding it to the end of the render. So, change this:

        def checklist_homepage(request):

            return render(
                request, 
                'checklist/checklist_homepage.html'
            )

    To this:

        def checklist_homepage(request):

            return render(
                request, 
                'checklist/checklist_homepage.html',
                { 'item': 'An Example Item' } 
            )

    Now let's move that to it's own variable which will call `context`. 


        def checklist_homepage(request):
            
            context = {
                'item': 'Another Example Item'
            }

            return render(
                request, 
                'checklist/checklist_homepage.html',
                context
            )


Now that we know how stuff moves, let's pass a list. 

Note that we change `item` to `items` for clarity. 


    in the template:

        When we request "items" we get: {{ items }}

    in the view:


        from django.shortcuts import render

        def checklist_homepage(request):

            items = [ 
                "First Item", "Second Item","Third Item"
            ]
            
            context = {
                'items': items
            }

            return render(
                request, 
                'checklist/checklist_homepage.html',
                context
            )


    TKTKTKTKT: note the pattern of:
        - load items
        - put items in context
        - put context in render



The output shows our list:

    When we request "items" we get: ['First Item', 'Second Item', 'Third Item']

We can loop over the list like this:

        <pre>
        {% for item in items %}
        {{ item.id }} - {{ item }}
        {% endfor %}
        </pre>



Now we're ready to bring in our data. We're going to make use of a Django specific call. 


    Add this:

        from .models import ChecklistItem

    Then switch out items to:

    
        items = ChecklistItem.objects.all()


    Temporarily Remove `from .models import ChecklistItem` Just so you know what happens if you forget to put in


That's it, your data is showing up on the page. 

Huzzah!

-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
--




-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
--


More notes (originally from index-2.md)

Django 3 Tutorial - Linking Checklist

Now let's get the links going. 

Note that the items we pulled in are full objects. Each object has an ID. (the reason we're seeing the string is the `__str__` method we added a while ago.)

To see the id, just do this: item.id inside the `{{ }}` tags

        <pre>
        {% for item in items %}
        {{ item.id }} - {{ item }}
        {% endfor %}
        </pre>

Change to this:

        {% extends 'base.html' %}

        {% block body_block %}
        <h3>Checklist Page</h3>

        <ul>
        {% for item in items %}
        <li>
            <a href="/checklist/{{ item.id }}">{{ item }}</a>
        </li>
        {% endfor %}

        {% endblock %}

That gets the links going. Click on one and we'll see our next error (A 404 page not found)


Fix:

    /Users/alans/django/tutorial_site/checklist/urls.py

    To add:

        path('<int:checklist_item_id>', views.checklist_item),

    So

        urlpatterns = [
            path('<int:checklist_item_id>', views.checklist_item),
            path('', views.checklist_homepage, name="checklist_homepage"),
        ]    



Error:

        AttributeError: module 'checklist.views' has no attribute 'checklist_item'


    Fix:

        def checklist_item(request, checklist_item_id):
            context = {}
            return render(request, 'checklist/checklist_item.html', context)


    TODO: Talk about how `checklist_item_id` sends to the parameter

Error in browser:

        TemplateDoesNotExist at /checklist/1
        checklist/checklist_item.html

    Make:

        touch /Users/alans/django/tutorial_site/checklist/templates/checklist/checklist_item.html

    The page will render, but there's nothing there. 

        http://127.0.0.1:8000/checklist/1

    Note that the number may be different if you deleted checklist items. 

    Add this into the template:

        {% extends 'base.html' %}

        {% block body_block %}
        <h2>Checklist Item</h2>
        {% endblock %}




Now we can add our stuff to it with this inside:

    /Users/alans/django/tutorial_site/checklist/views.py


    This:

        from django.shortcuts import render
    
    to this:
    
        from django.shortcuts import get_object_or_404, render

    And get the function to look like this:

        def checklist_item(request, checklist_item_id):
            
            checklist_item = get_object_or_404(
                ChecklistItem, 
                pk=checklist_item_id
            )

            context = { "checklist_item": checklist_item }

            return render(
                request, 
                'checklist/checklist_item.html', 
                context
            ) 

    Full file looks like:


        from django.shortcuts import get_object_or_404, render

        from .models import ChecklistItem 

        def checklist_homepage(request):

            checklist_items = ChecklistItem.objects.all()

            context = {
                'checklist_items': checklist_items
            }

            return render(
                request, 
                'checklist/checklist_homepage.html',
                context
            )


        def checklist_item(request, checklist_item_id):
            
            checklist_item = get_object_or_404(
                ChecklistItem, 
                pk=checklist_item_id
            )

            context = { "checklist_item": checklist_item }

            return render(
                request, 
                'checklist/checklist_item.html', 
                context
            ) 


-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
--

Update Homepage URL (might want to move this)

    <ul>
    {% for checklist_item in checklist_items %}
    <li>
         <a href="{% url 'checklist:checklist_item' checklist_item.id %}">{{ checklist_item }}</a>
    </li>
    {% endfor %}
    </ul>


With urls.py looking like:

    from django.urls import path

    from . import views

    app_name = 'checklist'

    urlpatterns = [
        path('<int:checklist_item_id>', views.checklist_item, name="checklist_item"),
        path('', views.checklist_homepage, name="checklist_homepage"),
    ]    


-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
--


Make an edit page:

First, create a link in /Users/alans/django/tutorial_site/checklist/templates/checklist/checklist_item.html

    <a href="{% url 'checklist:edit_checklist_item' checklist_item.id %}">Edit</a>

Error in the browser:

        NoReverseMatch at /checklist/1
        Reverse for 'edit_checklist_item' not found. 'edit_checklist_item' is not a valid view function or pattern name.

Fix:

    In:
        /Users/alans/django/tutorial_site/checklist/urls.py

    Add:

        path('<int:checklist_item_id>/edit/', views.edit_checklist_item, name="edit_checklist_item"), 

    TODO: Figure out the best practies for url structures. 

Error (Terminal):

      File "/Users/alans/django/tutorial_site/checklist/urls.py", line 8, in <module>
        path('edit/<int:checklist_item_id>', views.edit_checklist_item, name="edit_checklist_item"),
    AttributeError: module 'checklist.views' has no attribute 'edit_checklist_item'


Add this to /Users/alans/django/tutorial_site/checklist/views.py


    def edit_checklist_item(request, checklist_item_id):

        checklist_item = get_object_or_404(
            ChecklistItem, 
            pk=checklist_item_id
        )

        context = { "checklist_item": checklist_item }

        return render(
            request, 
            'checklist/edit_checklist_item.html', 
            context
        ) 



Error (Browser):

        TemplateDoesNotExist at /checklist/edit/1
        checklist/edit_checklist_item.html

Fix:


        echo "here" > /Users/alans/django/tutorial_site/checklist/templates/checklist/edit_checklist_item.html

    TKTKTKTKTK: If you use `echo` explian it, up front.

    Update it with this html:

        {% extends 'base.html' %}

        {% block body_block %}
        <h2>Edit Checklist Item</h2>
        <form action="" method="post">
            {% csrf_token %}
            <input type="text" name="checklist_update_text" value="{{ checklist_item.checklist_text }}">
            <input type="submit" value="Update">
        </form>
        {% endblock %}

    This just gets us working. To see it, now we can update the form call with this which will do an expected break:


    <form action="{% url 'checklist:update_checklist_item' checklist_item.id %}" method="post">



Error:

        NoReverseMatch at /checklist/edit/1
        Reverse for 'update_checklist_item' not found. 'update_checklist_item' is not a valid view function or pattern name.


Fix:

    Add this to /Users/alans/django/tutorial_site/checklist/urls.py

        path('<int:checklist_item_id>/update/', views.update_checklist_item, name="update_checklist_item"),

Error:

          File "/Users/alans/django/tutorial_site/checklist/urls.py", line 8, in <module>
            path('<int:checklist_item_id>/update/', views.update_checklist_item, name="update_checklist_item"),
        AttributeError: module 'checklist.views' has no attribute 'update_checklist_item'

Fix:

    This time, we're going to make a redirect first since the update won't actually have a page. 

    Add this to the top of /Users/alans/django/tutorial_site/checklist/views.py

        from django.http import HttpResponseRedirect
        from django.urls import reverse    

    And add this new function:

        def update_checklist_item(request, checklist_item_id):
            checklist_item = get_object_or_404(ChecklistItem, pk=checklist_item_id)
            checklist_item.checklist_text = request.POST['checklist_update_text']
            checklist_item.save()
            return HttpResponseRedirect(reverse('checklist:checklist_item', args=(checklist_item_id,)))

    Make sure that you have that last comma before the close parenthesis at:

        args=(checklist_item_id,)))


    The full file should now look like:

        from django.shortcuts import get_object_or_404, render
        from django.http import HttpResponseRedirect
        from django.urls import reverse    

        from .models import ChecklistItem 

        def checklist_homepage(request):
            checklist_items = ChecklistItem.objects.all()
            context = {'checklist_items': checklist_items }
            return render(
                request, 'checklist/checklist_homepage.html', context
            )


        def checklist_item(request, checklist_item_id):
            checklist_item = get_object_or_404( 
                ChecklistItem, 
                pk=checklist_item_id
            )
            context = { "checklist_item": checklist_item }
            return render(
                request, 'checklist/checklist_item.html', context
            ) 


        def edit_checklist_item(request, checklist_item_id):
            checklist_item = get_object_or_404(
                ChecklistItem, 
                pk=checklist_item_id
            )
            context = { "checklist_item": checklist_item }
            return render(
                request, 'checklist/edit_checklist_item.html', context
            ) 




        def update_checklist_item(request, checklist_item_id):
            checklist_item = get_object_or_404(
                ChecklistItem, 
                pk=checklist_item_id
            )
            checklist_item.checklist_text = request.POST['checklist_update_text']
            checklist_item.save()
            return HttpResponseRedirect(
                reverse('checklist:checklist_item', args=(checklist_item_id,))
            )


TKTKTKT: Put in:

        {% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}

    With an example:





-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
--


Orignally from "original_notes_v2.md"

TKTKTKT: Figure out where to put this:

Django Basic URL structure:

- ./checklist/ - the index page
- ./checklist/1 - detail pages. 
- ... add
- ... update
- ... delete





Now, working on edit, 

Put link on individul page to that points to the edit page:

    e.g. <a href="/checklist/{{ checklist_item.id }}/edit">Edit</a>

    TKTK find a better way to do that. 

Error: 404:


Fix: Update urls.py

    path('<int:checklist_item_id>/edit', views.checklist_item_edit, name='checklist_item_edit'),


New Error:

    AttributeError: module 'checklist.views' has no attribute 'checklist_item_edit'

Fix: add to views: 

    def checklist_item_edit(request, checklist_item_id):
        checklist_item = get_object_or_404(ChecklistItem, pk=checklist_item_id)
        context = {"checklist_item": checklist_item}
        return render(request, 'checklist/checklist_item_edit.html', context)

New error in browser:

        TemplateDoesNotExist at /checklist/1/edit
        checklist/checklist_item_edit.html

Fix: add template:

    checklist_item_edit.html


        {% extends 'base.html' %}

        {% block body_block %}
        <h2>Edit Checklist Item</h2>
        <form action="{% url 'checklist:checklist_item_update' checklist_item.id %}" method="post">
            {% csrf_token %}
            <input type="text" value="{{ checklist_item.checklist_text }}">
            <input type="submit" value="Update">
        </form>
        {% endblock %}


TODO: Make sure you've explained how URLs work and use the numbers here. 
TODO: Figure out when you need to add this to checklist/urls.py

        app_name = 'checklist'


Error:

    NoReverseMatch at /checklist/1/edit
Reverse for 'checklist_item_update' not found. 'checklist_item_update' is not a valid view function or pattern name.

    Need this:

        path('<int:checklist_item_id>/update', views.checklist_item_update, name='checklist_item_update'),

Then you'd get this error:

    AttributeError: module 'checklist.views' has no attribute 'checklist_item_update'

which you fix with a view, which we want to do this with. 


Start with a redirect before editing the data:

    in views.py add these:

        from django.http import HttpResponseRedirect
        from django.urls import reverse

    and then this for the new function in views.py

        def checklist_item_update(request, checklist_item_id):
            checklist_item = get_object_or_404(ChecklistItem, pk=checklist_item_id)
            checklist_item.checklist_text = request.POST['checklist_update_text']
            checklist_item.save()
            return HttpResponseRedirect(reverse('checklist:checklist_item', args=(checklist_item_id,))) 

    Make sure to have that last comma in `args=(checklist_item_id,)` otherwise things will explode. 





-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
-- hr
--


Originally from "original_notes.md"


October 9, 2020 - working session:

Testing:

- NOTE: The mozilla tutorial says to make `example/checklist/tests/test_models.py`, but that isn't found when you run `python manage.py test`. 

- NOTE: Don't really use this, it's just for your reference. Skip to the next item. This is the example code with a failing test. file example/checklist/test.py so it looks like this (TKTKTK - refine this down):

    from django.test import TestCase

    class YourTestClass(TestCase):
        @classmethod
        def setUpTestData(cls):
            print("setUpTestData: Run once to set up non-modified data for all class methods.")
            pass

        def setUp(self):
            print("setUp: Run once for every test method to setup clean data.")
            pass

        def test_false_is_false(self):
            print("Method: test_false_is_false.")
            self.assertFalse(False)

        def test_false_is_true(self):
            print("Method: test_false_is_true.")
            self.assertTrue(False)

        def test_one_plus_one_equals_two(self):
            print("Method: test_one_plus_one_equals_two.")
            self.assertEqual(1 + 1, 2)

- This is how you do a models test `example/checklist/test.py`. 

        from django.test import TestCase

        from checklist.models import ChecklistItem

        class ChecklistItemTest(TestCase):

            def test_checklist_text_works(self):
                ChecklistItem.objects.create(checklist_text='Make this work')
                expected = 'Make this work'
                actual = ChecklistItem.objects.get(id=1).checklist_text
                self.assertEquals(expected, actual)

    TODO: Exapliain this and everything else line by line. 



- Move the object creation into its own function:

        from django.test import TestCase

        from checklist.models import ChecklistItem

        class ChecklistItemTest(TestCase):
            @classmethod
            def setUpTestData(cls):
                ChecklistItem.objects.create(checklist_text='Make this work')

            def test_checklist_text_works(self):
                expected = 'Make this work'
                actual = ChecklistItem.objects.get(id=1).checklist_text
                self.assertEquals(expected, actual)

- Now, let's do a red green test pattern. TKTKTKT, these need to be refined. 

First, create a new test that checks to see if a boolean field exists. 

    # Intentional Red to Prove the Test is wired up properly
    def test_is_done(self):
        expected = True
        actual = False
        self.assertEquals(expected, actual)

    # Shameless Green to get back to green
    def test_is_done(self):
        expected = True
        actual = True
        self.assertEquals(expected, actual)


    # Fails because we haven't added it yet.
    def test_is_done(self):
        expected = True
        actual = ChecklistItem.objects.get(id=1).is_done 
        self.assertEquals(expected, actual)

    AttributeError: 'ChecklistItem' object has no attribute 'is_done'

    - now add it with shameless green as a simple return true. 

        is_done = True 

    Now, we're green again and have a backstop to do our work under. 

    The first thing we're going to do is add a line that sets is_done to a model value from Django. 

    It's going to fail. Don't be surprised about that. This is effectively a way to make a test go red. That's what we want for the next step in our cycle. 

        is_done = False 
        is_done = models.BooleanField(default=False)

    We're leaving `is_done = False` in there so we can get back to it (and Green) any time we want. 

    But, when we run tests with `is_done = models.BooleanField(default=True)` in place, it'll bomb with a bunch of text that ends something like:

        django.db.utils.OperationalError: no such column: checklist_checklistitem.is_done

    This is a sign that we haven't created and run the migrations that tell the database about the updated fields in the model. 

    We tell Django that we want to make migrations for the checklist model with:

        python manage.py makemigrations checklist

    And then apploy those migrations with:

        python manage.py migrate






-- hr


TKTKTKT Add this somewhere:

Show the is_done boolean by removing `admin.site.register(ChecklistItem)` and adding this to example/checklist/admin.py

        @admin.register(ChecklistItem)
        class ChecklistItemAdmin(admin.ModelAdmin):
            list_display = ('checklist_text', 'is_done')

    So, it'll look like this:

        from django.contrib import admin
        from .models import ChecklistItem

        @admin.register(ChecklistItem)
        class ChecklistItemAdmin(admin.ModelAdmin):
            list_display = ('checklist_text', 'is_done')


TKTKTKT: add this in the approprate place:

    Upadgte views.py to have this: 
        
        from django.shortcuts import get_object_or_404, render

    so that this `get_object_or_404` works. 





-- hr


October 10, 2020

Testing: Views:

TODO: See if you can get this test structure to work:

    catalog/
      /tests/
        __init__.py
        test_models.py
        test_forms.py
        test_views.py

- Next setup checklist_item:


    TODO: Setup link in checklist/templates/checklist/index.html to point to individual checklist first so you can see the error message. 




    Build template with:

        <html><body>checklist item</body></html>


    Add view method:

        def checklist_item(request, checklist_item_id):
            context = {}
            return render(request, 'checklist/checklist_item.html', context)    

    Add url for checklist/urls.py


        urlpatterns = [
            path('<int:checklist_item_id>', views.checklist_item, name='checklist_item'),
            path('', views.index, name='index'),
        ]    

        That's how you pass the number. 


-- hr


Making the forms:

    Create: example/checklist/forms.py with

    with:

        from django import forms
            
        class UpdateChecklistItemName(forms.Form):
            checklist_text = forms.CharField(
                help_text="Update it."
            )

TODO: Make a page that you can target with edit. 

Things you did to work through the order of:

    add this to checklist/views.py


        from checklist.forms import UpdateChecklistItemName

    switched views to have this (needs work)

        def checklist_item(request, checklist_item_id):
            checklist_item_object = get_object_or_404(ChecklistItem, pk=checklist_item_id)

            context = {
                'checklist_item_object': checklist_item_object,
                'form': UpdateChecklistItemName(
                    initial={'checklist_text': checklist_item_object.checklist_text }
                ),
                
            }
            return render(request, 'checklist/checklist_item.html', context)    

    TODO: Clean up the form. 


    Add this to views:

        from django.http import HttpResponseRedirect
        from django.urls import reverse