This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Preps

This class is flipped, it’s vital that you prepare for the in-class activities by completing these preparations.

1 - Prep 01: Setup Dev Environment and Creating a skeleton website

  1. Python via uv
  2. Django 6.1.
  3. MDN Django Tutorial through Part 2
  4. Put it on github

Prereq

  1. Create a directory for 347 work on your local machine. See our docs on:
    1. Working Locally vs. Remotely
      1. 🚨 Complete the auth via keypairs steps
    2. Files

Instructions

  1. Do the following (“do” as in read, take notes, and try to make work) from our fork of the MDN Django Tutorial:
    1. Django introduction
    2. Setting up a Django development environment
    3. Django Tutorial: The Local Library website
    4. Django Tutorial Part 2: Creating a skeleton website

Submitting

  1. After you complete the steps linked above, you will have pushed some commits to a github repository.
  2. Github provides a link for each commit.
  3. Submit a commit.url file containing the url to the latest commit of your public github repo that was relevant to the steps above to gradescope.
  4. Here’s an example of what such a URL might look like https://github.com/347s26/locallibrary-hcientist/commit/caca9ecbcc79f5aa3ec29300c47c123b9a7a0ca5. See the screenshot below to see where to click to reach the right kind of URL. screenshot showing most recent commit link

2 - Prep 02: Django Models and admin from MDN Tutorial Parts 3-4

MDN Django Tutorial Parts 3-4

Continuing to work in the repo you created for Prep 01.

Part 3

  1. Do Part 3 (Models)
  2. Question: what is a migration? did we need any yet? what alternative could there have been? how do they work?! e.g.
    1. how does django know what to put in a migration?
    2. how does django know which migrations have been applied?
  3. Read “Falsehoods Programmers Believe About Names – With Examples”
    1. Based on the points in the article, what (if any) changes might you propose to the models in this Prep?
    2. Have you ever been unsure how to enter information about yourself into a computer system?
      1. What information were you trying to enter?
      2. Do you recall what system?
      3. What was the cause of your uncertainty?
      4. (reflect on this personally, and if you have reflections that you wish to share that don’t doxx you, feel free to bring them to in-class discussion)
  4. Consider installing some database tools:
    1. GOAT: DBeaver Community (supports pretty much every kind of db ever)
    2. db-specific:
      1. DB Browser for SQLite
      2. PGAdmin for PostgresQL (we don’t need the PostgresQL stuff yet)

Best Practices: env files (for repeatability, secrets, and great good)

env files help track configuration information. Often these values may differ on different machines of your own or at least between you and a team member, and likely also between your machine for (local) development and your server(s) in the cloud that hosts your app.

Because it might be convenient to discard and recreate your database on occasion, and also to have consistent expectations for troubleshooting across all the students’ computers, make these files so that we don’t have to waste time waiting for you to re-find your django admin user’s password.

  1. Ensure that your .gitignore
    1. exists at all 😆
      1. if it doesn’t, maybe start from a good Python .gitignore template such as that provided by github
    2. has .env in it
  2. Create a file named .env.example in the root of your project (so it’s a sibling of catalog/, locllibrary_config/, and manage.py) with this code:
    DJANGO_SUPERUSER_USERNAME="me"
    DJANGO_SUPERUSER_PASSWORD="me"
    DJANGO_SUPERUSER_EMAIL="me@me.me"
    
  3. save a copy of that file as .env

We could add other things here, but this is all we need for now.

Instruct django project to use env file

  1. add django-environ as a dependency: uv add django-environ
  2. edit settings.py to
    1. import environ at the top
    2. immediately after the definition of BASE_DIR, add the following
      env = environ.Env()
      # FYI: OS environment variables take precedence over variables from .env
      env.read_env(str(BASE_DIR / ".env"))
      

create superuser with values from env

With this env file created and expected in your settings, you can add an argument to the django-admin createsuperuser command to have it default to these values:

uv run python manage.py createsuperuser --no-input

Part 4

  1. Do Part 4 (admin site).

Submitting

  1. Push your updated code back to your github repo
  2. submit the most recent commit URL to the gradescope assignment in a file called commit.url.

3 - Prep 03: Django Admin Site and Home Page from MDN Tutorial Part 5

MDN Django Tutorial Part 5
  1. Continuing to work in the repo you created for Prep 01, and extended in Prep 02, complete MDN Django Tutorial Part 5.
    • Note: Near the end of Part 5, the tutorial fails to mention that if you have had your development server (the one you get with the runserver command) running the whole time you were working on this Part, you’ll have to kill it and start it again before you can expect django to find your new template.
  2. Push your updated code back to the github repo
  3. submit the most recent commit URL to the gradescope assignment.

4 - Prep 04: List and Detail Views from MDN Tutorial Part 6

MDN Django Tutorial Part 6

MDN Django Tutorial

So far

By now, you should have a django web application that:

  1. you can run locally
  2. has several library-related models that are persisted to a database
    1. this database has a few instances of the various models
  3. has a super user (admin)
  4. has admin pages that the admin can login to and see (and modify) the current instances in the database
  5. has a templated home page that renders static content as well as dynamic content retrieved from the database.

Next Steps

  1. Continuing to work in the repo you created and connected to github for the previous preps, complete MDN Django Tutorial Part 6.
  2. Push your updated code back to the github repo
  3. submit the most recent commit URL to the gradescope assignment

5 - Prep 05: DX, Sessions and Permissions from MDN Tutorial Parts7-8

Developer Experience & Improving on MDN Django Tutorial Data Model

DX - Developer Experience

“DX” or Developer Experience refers to features of tools for software developers that help them do their job well. In this prep we will introduce several and apply them to our locallibrary.

Django Admin Shell

Django’s Admin command-line utility provides tons of useful features. In this proep, we’ll use its shell to interact with your application. It can be helpful to use the shell as a REPL to confirm that the python you write for django works as you expect.

Let’s use the shell to perform CRUD actions with the models you’ve defined.

  1. launch the django admin shell by running
    python manage.py shell
    
  2. list all the genres in your database
    all_genres = Genre.objects.all()
    all_genres
    
    • Note: the django admin shell automatically imports all models from all INSTALLED_APPS. This is why we didn’t have to explicitly import Genre for this to succeed.
  3. create a new genre called “Food Science”
    food = Genre.objects.create(name="Food Science")
    food.id # this should show you the internal unique id of this new instance of the Genre class
    
  4. again list all the genres (and confirm you see the Food Science genre)
  5. search for the genre that has the name Food Science
    byebyebye = Genre.objects.get(name="Food Science")
    
  6. let’s see that we can delete instances by deleting this new genre
    byebyebye.delete()
    all_genres
    
    • you should see that the model instance’s delete function returned a tuple showing that 1 instance was deleted and what class that instance belonged to.
    • Note: you should see that evaluating all_genres shows the correct remaining set of instances of the Genre class even though we didn’t update it after invoking the instance’s delete(). As the docs explain, the QuerySet class only actually interacts with the database when the object is evaluated. When we earlier evaluated all_genres it actually asked the database for the current situation, but then we we again evaluated allgenres just now, it again communicated with the database automatically.
  7. You can exit the django shell the same way you usually exit the python shell, e.g.
    exit()
    

pyproject.toml

What/Why?

By keeping a list of a project’s dependencies in a pyproject.toml file, you can use source control to track the changes over time, and if you need to run your project in a new environment (e.g. on a different computer of your own, on your team member’s computer, or in the cloud when you deploy your project), you won’t have to go read your diary for all the uv add commands.

How?

Instead of running uv add Django>=6.1 or any other uv add PACKAGE commands directly, you should instead add the package to your pyproject.toml and then run uv sync. Or, you can use uv add PACKAGE which does both in one step.

Create pyproject.toml
  1. create a new file called pyproject.toml in the root of your locallibrary. Note: this file should be a sibling to .venv/, locallibrary/, manage.py, and db.sqlite3
  2. give it this content
    [project]
    name = "locallibrary"
    version = "0.1.0"
    requires-python = ">=3.14"
    dependencies = [
        "Django>=6.1",
    ]
    
Install dependencies from pyproject.toml

The installer (uv) shouldn’t reinstall packages that you already have so if you already have some dependencies installed and then add one new dependency to your pyproject.toml, then tell it to install again, it shouldn’t do redundant installs, but only actually install the new dependency.

To install the dependencies (that you don’t already have) from the pyproject.toml, run:

uv sync

Data Migrations

As first introduced in Part 2 of the MDN Django Tutorial, migrations are used to update the database when we make changes to our models. So new models, removal of models, and changes to models should result in the creation of migrations when we run the makemigrations command.

In addition to creating migrations for changes to the structure of our data model (in db land, they’d say schema changes), it can be helpful to provide necessary instance of our models to our application programmatically (i.e. rather than interactively clicking through a ton of the admin forms or having to write sql queries to do so). Django calls this a data migration.

  1. it’s helpful for django to create the “empty” migration file into which we will write our commands
    uv run manage.py makemigrations --empty catalog
    
    • Note: you should see output that s`hows the path to the newly created migration file.
  2. Open the new file in your editor. You’ll see that there’s not much in there. The main thing that’s useful is that django assumes we might like the new migration to depend on the most recent migration in the same app, which is correct in our case, so don’t modify the dependencies list it created. Instead add an entry to the operations list.
    migrations.RunPython(seed_books),
    
    • Note: The docs on RunPython explain among other things that we can write reversible or irreversible migrations.
  3. Now we need to write the seed_books function we referred to. At the top of your file, after the imports but before the definition of the Migration class begins, define the seed_books function. Why not steal mine this time?
    • Note:
      1. this code looks like code that we could have written in the django shell. Mostly it is! It’s using the model and QuerySet functions like in our exploration of the shell above, but there’s a super cool difference, and it’s subtle but so powerful: instead of importing the models the typical python way (i.e. from catalog.models import Book), we use apps.get_model(). The difference is that apps.get_model is history-aware while the regular import is anachronistic. When we write this migration, it could be that the Book (model) class has a certain structure (or schema) that might not be the same in the future. We would like to write this migration file in a way that it can succeed not only at the time of authoring, but that it will also succeed even if we eventually remove a field from the Book class that we are referencing in this file, or even if Book comes to have a new required field that we aren’t populating in this file.
  4. Add all these objects to our database by migrating
    python manage.py migrate
    
    • there should be output that suggests this was successful, e.g. 11 objects imported automatically
  5. Confirm that the migration worked. In the django shell (do you remember how to get in there?), do
    da = Author.objects.get(first_name="Douglas", last_name="Adams")
    da_books = da.book_set.all()
    da_books
    

MDN Django Tutorial

Improve the Data Model

Look at this ridiculous excerpt from the Book model as created in Part 3 of the tutorial.

...
class Book(models.Model):
    ...
    author = models.ForeignKey('Author', on_delete=models.RESTRICT, null=True)
    # Foreign Key used because book can only have one author, but authors can have multiple books.
    # Author as a string rather than object because it hasn't been declared yet in file.
    ...

Do you see that?!

because book can only have one author, but authors can have multiple books

Admit it. That’s outrageous! Books can totally have multiple authors! Let’s fix it!

  1. create a BookAuthor model that has
    1. a foreign key to Book
    2. a foreign key to Author
    3. an integer field named author_order
  2. update the Book model to (keep author for now and also) have authors. Declare it as a ManyToManyField, but unlike the one you already have for Genre:
    1. use the through parameter to explicitly tell django that the intermediate model is your newly created BookAuthor
    2. use the related_name parameter to tell django that when referencing all their books from an author instance, you want the accessor to be called just books (otherwise it will default to book_set and collide with the existing accessor created by Book’s author foreign key field)
  3. Having added a new model and changed an existing model, tell django to makemigrations and to migrate
  4. now remove the old author field from the Book model and run (ONLY) makemigrations (this will likely fail because we are referencing author from book in admin.py). If it does:
    1. remove the author field from the list_display of BookAdmin
    2. remove the BookInline from AuthorAdmin
    3. run (ONLY) makemigrations
  5. we need to edit this newly generated migration (it should be in locallibrary/catalog/migrations) to record our existing book-author relationships as BookAuthor instances before it discards them!
  6. make a new function before the definition of the Migration class in the new migration file.
    def authorize_books(apps, schema_editor): # "author-ize" get it X-D
        Author = apps.get_model("catalog", "Author")
        Book = apps.get_model("catalog", "Book")
        BookAuthor = apps.get_model("catalog", "BookAuthor")
    
        # TODO add code here to create a new BookAuthor for each book
    
  7. add your new function as the first operation in the list of operations in the Migration class (look back at one of your data migrations if [like me 😅] you don’t remember how)
  8. tell django to migrate
  9. add BookAuthor to your admin site
  10. create a BookAuthorInline and include it on Author and Book
  11. update your templates to do the right thing now that there can be multiple authors
  12. Push your updated code back to the github (classroom) repo
  13. submit the most recent commit URL to the canvas assignment