From f7f7ccaf09b65b66e0c995e6df53888288411770 Mon Sep 17 00:00:00 2001 From: Hamish Willee Date: Wed, 28 Feb 2024 16:13:14 +1100 Subject: [PATCH] Django 5 update (From Django 4) (#32406) * Add info about Django 5 update * Global search-replace of django urls from 4 to 5 * Dev env complete * Creating a skeleton website * Add more changes to skeleton tute * Models - update to mention constraints, as these are useful * Bootstrap, logout form, fixes for url to django-githubrepo base * Updates requirements and remainder of forms --- .../server-side/django/admin_site/index.md | 22 +-- .../django/authentication/index.md | 103 ++++++++------ .../server-side/django/deployment/index.md | 39 ++--- .../django/development_environment/index.md | 33 +++-- .../django/django_assessment_blog/index.md | 2 +- .../learn/server-side/django/forms/index.md | 134 +++++++++--------- .../server-side/django/generic_views/index.md | 46 +++--- .../server-side/django/home_page/index.md | 36 ++--- .../server-side/django/introduction/index.md | 2 +- .../learn/server-side/django/models/index.md | 81 ++++++----- .../server-side/django/sessions/index.md | 16 +-- .../django/skeleton_website/index.md | 102 +++++++------ .../learn/server-side/django/testing/index.md | 28 ++-- .../django/web_application_security/index.md | 18 +-- 14 files changed, 357 insertions(+), 305 deletions(-) diff --git a/files/en-us/learn/server-side/django/admin_site/index.md b/files/en-us/learn/server-side/django/admin_site/index.md index 89b8e4af0ff7a82..3f2a95201ce3497 100644 --- a/files/en-us/learn/server-side/django/admin_site/index.md +++ b/files/en-us/learn/server-side/django/admin_site/index.md @@ -31,13 +31,13 @@ Now that we've created models for the [LocalLibrary](/en-US/docs/Learn/Server-si The Django admin _application_ can use your models to automatically build a site area that you can use to create, view, update, and delete records. This can save you a lot of time during development, making it very easy to test your models and get a feel for whether you have the _right_ data. The admin application can also be useful for managing data in production, depending on the type of website. The Django project recommends it only for internal data management (i.e. just for use by admins, or people internal to your organization), as the model-centric approach is not necessarily the best possible interface for all users, and exposes a lot of unnecessary detail about the models. -All the configuration required to include the admin application in your website was done automatically when you [created the skeleton project](/en-US/docs/Learn/Server-side/Django/skeleton_website) (for information about actual dependencies needed, see the [Django docs here](https://docs.djangoproject.com/en/4.2/ref/contrib/admin/)). As a result, all you **must** do to add your models to the admin application is to _register_ them. At the end of this article we'll provide a brief demonstration of how you might further configure the admin area to better display our model data. +All the configuration required to include the admin application in your website was done automatically when you [created the skeleton project](/en-US/docs/Learn/Server-side/Django/skeleton_website) (for information about actual dependencies needed, see the [Django docs here](https://docs.djangoproject.com/en/5.0/ref/contrib/admin/)). As a result, all you **must** do to add your models to the admin application is to _register_ them. At the end of this article we'll provide a brief demonstration of how you might further configure the admin area to better display our model data. After registering the models we'll show how to create a new "superuser", login to the site, and create some books, authors, book instances, and genres. These will be useful for testing the views and templates we'll start creating in the next tutorial. ## Registering models -First, open **admin.py** in the catalog application (**/locallibrary/catalog/admin.py**). It currently looks like this — note that it already imports `django.contrib.admin`: +First, open **admin.py** in the catalog application (**/django-locallibrary-tutorial/catalog/admin.py**). It currently looks like this — note that it already imports `django.contrib.admin`: ```python from django.contrib import admin @@ -138,13 +138,13 @@ You can further customize the interface to make it even easier to use. Some of t In this section we're going to look at a few changes that will improve the interface for our _LocalLibrary_, including adding more information to `Book` and `Author` model lists, and improving the layout of their edit views. We won't change the `Language` and `Genre` model presentation because they only have one field each, so there is no real benefit in doing so! -You can find a complete reference of all the admin site customization choices in [The Django Admin site](https://docs.djangoproject.com/en/4.2/ref/contrib/admin/) (Django Docs). +You can find a complete reference of all the admin site customization choices in [The Django Admin site](https://docs.djangoproject.com/en/5.0/ref/contrib/admin/) (Django Docs). ### Register a ModelAdmin class -To change how a model is displayed in the admin interface you define a [ModelAdmin](https://docs.djangoproject.com/en/4.2/ref/contrib/admin/#modeladmin-objects) class (which describes the layout) and register it with the model. +To change how a model is displayed in the admin interface you define a [ModelAdmin](https://docs.djangoproject.com/en/5.0/ref/contrib/admin/#modeladmin-objects) class (which describes the layout) and register it with the model. -Let's start with the `Author` model. Open **admin.py** in the catalog application (**/locallibrary/catalog/admin.py**). Comment out your original registration (prefix it with a #) for the `Author` model: +Let's start with the `Author` model. Open **admin.py** in the catalog application (**/django-locallibrary-tutorial/catalog/admin.py**). Comment out your original registration (prefix it with a #) for the `Author` model: ```python # admin.site.register(Author) @@ -186,7 +186,7 @@ Currently all of our admin classes are empty (see `pass`) so the admin behavior ### Configure list views -The _LocalLibrary_ currently lists all authors using the object name generated from the model `__str__()` method. This is fine when you only have a few authors, but once you have many you may end up having duplicates. To differentiate them, or just because you want to show more interesting information about each author, you can use [list_display](https://docs.djangoproject.com/en/4.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_display) to add additional fields to the view. +The _LocalLibrary_ currently lists all authors using the object name generated from the model `__str__()` method. This is fine when you only have a few authors, but once you have many you may end up having duplicates. To differentiate them, or just because you want to show more interesting information about each author, you can use [list_display](https://docs.djangoproject.com/en/5.0/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_display) to add additional fields to the view. Replace your `AuthorAdmin` class with the code below. The field names to be displayed in the list are declared in a _tuple_ in the required order, as shown (these are the same names as specified in your original model). @@ -270,7 +270,7 @@ In your website go to the author detail view — it should now appear as shown b #### Sectioning the detail view -You can add "sections" to group related model information within the detail form, using the [fieldsets](https://docs.djangoproject.com/en/4.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.fieldsets) attribute. +You can add "sections" to group related model information within the detail form, using the [fieldsets](https://docs.djangoproject.com/en/5.0/ref/contrib/admin/#django.contrib.admin.ModelAdmin.fieldsets) attribute. In the `BookInstance` model we have information related to what the book is (i.e. `name`, `imprint`, and `id`) and when it will be available (`status`, `due_back`). We can add these to our `BookInstanceAdmin` class as shown below, using the `fieldsets` property. @@ -299,7 +299,7 @@ Now navigate to a book instance view in your website; the form should appear as Sometimes it can make sense to be able to add associated records at the same time. For example, it may make sense to have both the book information and information about the specific copies you've got on the same detail page. -You can do this by declaring [inlines](https://docs.djangoproject.com/en/4.2/ref/contrib/admin/#django.contrib.admin.ModelAdmin.inlines), of type [TabularInline](https://docs.djangoproject.com/en/4.2/ref/contrib/admin/#django.contrib.admin.TabularInline) (horizontal layout) or [StackedInline](https://docs.djangoproject.com/en/4.2/ref/contrib/admin/#django.contrib.admin.StackedInline) (vertical layout, just like the default model layout). You can add the `BookInstance` information inline to our `Book` detail by specifying `inlines` in your `BookAdmin`: +You can do this by declaring [inlines](https://docs.djangoproject.com/en/5.0/ref/contrib/admin/#django.contrib.admin.ModelAdmin.inlines), of type [TabularInline](https://docs.djangoproject.com/en/5.0/ref/contrib/admin/#django.contrib.admin.TabularInline) (horizontal layout) or [StackedInline](https://docs.djangoproject.com/en/5.0/ref/contrib/admin/#django.contrib.admin.StackedInline) (vertical layout, just like the default model layout). You can add the `BookInstance` information inline to our `Book` detail by specifying `inlines` in your `BookAdmin`: ```python class BooksInstanceInline(admin.TabularInline): @@ -316,7 +316,7 @@ Now navigate to a view for a `Book` in your website — at the bottom you should ![Admin Site - Book with Inlines](admin_improved_book_detail_inlines.png) -In this case all we've done is declare our tabular inline class, which just adds all fields from the _inlined_ model. You can specify all sorts of additional information for the layout, including the fields to display, their order, whether they are read only or not, etc. (see [TabularInline](https://docs.djangoproject.com/en/4.2/ref/contrib/admin/#django.contrib.admin.TabularInline) for more information). +In this case all we've done is declare our tabular inline class, which just adds all fields from the _inlined_ model. You can specify all sorts of additional information for the layout, including the fields to display, their order, whether they are read only or not, etc. (see [TabularInline](https://docs.djangoproject.com/en/5.0/ref/contrib/admin/#django.contrib.admin.TabularInline) for more information). > **Note:** There are some painful limits in this functionality! In the screenshot above we have three existing book instances, followed by three placeholders for new book instances (which look very similar!). It would be better to have NO spare book instances by default and just add them with the **Add another Book instance** link, or to be able to just list the `BookInstance`s as non-readable links from here. The first option can be done by setting the `extra` attribute to `0` in `BooksInstanceInline` model, try it by yourself. @@ -333,7 +333,7 @@ That's it! You've now learned how to set up the administration site in both its ## Further reading -- [Writing your first Django app, part 2: Introducing the Django Admin](https://docs.djangoproject.com/en/4.2/intro/tutorial02/#introducing-the-django-admin) (Django docs) -- [The Django Admin site](https://docs.djangoproject.com/en/4.2/ref/contrib/admin/) (Django Docs) +- [Writing your first Django app, part 2: Introducing the Django Admin](https://docs.djangoproject.com/en/5.0/intro/tutorial02/#introducing-the-django-admin) (Django docs) +- [The Django Admin site](https://docs.djangoproject.com/en/5.0/ref/contrib/admin/) (Django Docs) {{PreviousMenuNext("Learn/Server-side/Django/Models", "Learn/Server-side/Django/Home_page", "Learn/Server-side/Django")}} diff --git a/files/en-us/learn/server-side/django/authentication/index.md b/files/en-us/learn/server-side/django/authentication/index.md index 052d4919d316a2b..aee01849ec571ef 100644 --- a/files/en-us/learn/server-side/django/authentication/index.md +++ b/files/en-us/learn/server-side/django/authentication/index.md @@ -43,7 +43,7 @@ The authentication was enabled automatically when we [created the skeleton websi > **Note:** The necessary configuration was all done for us when we created the app using the `django-admin startproject` command. The database tables for users and model permissions were created when we first called `python manage.py migrate`. -The configuration is set up in the `INSTALLED_APPS` and `MIDDLEWARE` sections of the project file (**locallibrary/locallibrary/settings.py**), as shown below: +The configuration is set up in the `INSTALLED_APPS` and `MIDDLEWARE` sections of the project file (**django-locallibrary-tutorial/locallibrary/settings.py**), as shown below: ```python INSTALLED_APPS = [ @@ -97,7 +97,7 @@ Our superuser is already authenticated and has all permissions, so we'll need to > user.save() > ``` > -> For more information, see [Using a custom user model when starting a project](https://docs.djangoproject.com/en/4.2/topics/auth/customizing/#using-a-custom-user-model-when-starting-a-project) (Django docs). +> For more information, see [Using a custom user model when starting a project](https://docs.djangoproject.com/en/5.0/topics/auth/customizing/#using-a-custom-user-model-when-starting-a-project) (Django docs). Below we'll first create a group and then a user. Even though we don't have any permissions to add for our library members yet, if we need to later, it will be much easier to add them once to the group than individually to each member. @@ -144,7 +144,7 @@ In this section, we show how to integrate the default system into the _LocalLibr ### Project URLs -Add the following to the bottom of the project urls.py file (**locallibrary/locallibrary/urls.py**) file: +Add the following to the bottom of the project urls.py file (**django-locallibrary-tutorial/locallibrary/urls.py**) file: ```python # Add Django site authentication urls (for login, logout, password management) @@ -155,10 +155,10 @@ urlpatterns += [ ``` Navigate to the `http://127.0.0.1:8000/accounts/` URL (note the trailing forward slash!). -Django will show an error that it could not find this URL, and list all the URLs it tried. -From this you can see the URLs that will work, for example: +Django will show an error that it could not find a mapping for this URL, and list all the URLs that it tried. +From this you can see the URLs that will work once we have created templates. -> **Note:** Using the above method adds the following URLs with names in square brackets, which can be used to reverse the URL mappings. You don't have to implement anything else — the above URL mapping automatically maps the below mentioned URLs. +> **Note:** Adding the `accounts/` path as shown above adds the following URLs, along with names (given in square brackets) that can be used to reverse the URL mappings. You don't have to implement anything else — the above URL mapping automatically maps the below mentioned URLs. > > ```python > accounts/ login/ [name='login'] @@ -179,7 +179,7 @@ Exception Type: TemplateDoesNotExist Exception Value: registration/login.html ``` -The next step is to create a registration directory on the search path and then add the **login.html** file. +The next step is to create a directory for the templates named "registration" and then add the **login.html** file. ### Template directory @@ -190,7 +190,7 @@ For this site, we'll put our HTML pages in the **templates/registration/** direc > **Note:** Your folder structure should now look like the below: > > ```plain -> locallibrary/ # Django project folder +> django-locallibrary-tutorial/ # Django top level project folder > catalog/ > locallibrary/ > templates/ @@ -198,9 +198,9 @@ For this site, we'll put our HTML pages in the **templates/registration/** direc > ``` To make the **templates** directory visible to the template loader we need to add it in the template search path. -Open the project settings (**/locallibrary/locallibrary/settings.py**). +Open the project settings (**/django-locallibrary-tutorial/locallibrary/settings.py**). -Then import the `os` module (add the following line near the top of the file). +Then import the `os` module (add the following line near the top of the file if it isn't already present). ```python import os # needed by code below @@ -222,7 +222,7 @@ Update the `TEMPLATES` section's `'DIRS'` line as shown: > **Warning:** The authentication templates provided in this article are a very basic/slightly modified version of the Django demonstration login templates. You may need to customize them for your own use! -Create a new HTML file called /**locallibrary/templates/registration/login.html** and give it the following contents: +Create a new HTML file called /**django-locallibrary-tutorial/templates/registration/login.html** and give it the following contents: ```django {% extends "base_generic.html" %} @@ -272,7 +272,7 @@ Navigate back to the login page (`http://127.0.0.1:8000/accounts/login/`) once y If you log in using valid credentials, you'll be redirected to another page (by default this will be `http://127.0.0.1:8000/accounts/profile/`). The problem is that, by default, Django expects that upon logging in you will want to be taken to a profile page, which may or may not be the case. As you haven't defined this page yet, you'll get another error! -Open the project settings (**/locallibrary/locallibrary/settings.py**) and add the text below to the bottom. Now when you log in you should be redirected to the site homepage by default. +Open the project settings (**/django-locallibrary-tutorial/locallibrary/settings.py**) and add the text below to the bottom. Now when you log in you should be redirected to the site homepage by default. ```python # Redirect to home URL after login (Default redirects to /accounts/profile/) @@ -281,9 +281,10 @@ LOGIN_REDIRECT_URL = '/' ### Logout template -If you navigate to the logout URL (`http://127.0.0.1:8000/accounts/logout/`) then you'll see some odd behavior — your user will be logged out sure enough, but you'll be taken to the **Admin** logout page. That's not what you want, if only because the login link on that page takes you to the Admin login screen (and that is only available to users who have the `is_staff` permission). +If you navigate to the logout URL (`http://127.0.0.1:8000/accounts/logout/`) then you'll get an error because Django 5 does not allow logout using `GET`, only `POST`. +We'll add a form you can use to logout in a minute, but first we'll create the page that users are taken to after logging out. -Create and open **/locallibrary/templates/registration/logged_out.html**. Copy in the text below: +Create and open **/django-locallibrary-tutorial/templates/registration/logged_out.html**. Copy in the text below: ```django {% extends "base_generic.html" %} @@ -294,7 +295,7 @@ Create and open **/locallibrary/templates/registration/logged_out.html**. Copy i {% endblock %} ``` -This template is very simple. It just displays a message informing you that you have been logged out, and provides a link that you can press to go back to the login screen. If you go to the logout URL again you should see this page: +This template is very simple. It just displays a message informing you that you have been logged out, and provides a link that you can press to go back to the login screen. The screen renders like this (after logout): ![Library logout page v1](library_logout.png) @@ -306,7 +307,7 @@ The following templates can be used as a starting point. #### Password reset form -This is the form used to get the user's email address (for sending the password reset email). Create **/locallibrary/templates/registration/password_reset_form.html**, and give it the following contents: +This is the form used to get the user's email address (for sending the password reset email). Create **/django-locallibrary-tutorial/templates/registration/password_reset_form.html**, and give it the following contents: ```django {% extends "base_generic.html" %} @@ -325,7 +326,7 @@ This is the form used to get the user's email address (for sending the password #### Password reset done -This form is displayed after your email address has been collected. Create **/locallibrary/templates/registration/password_reset_done.html**, and give it the following contents: +This form is displayed after your email address has been collected. Create **/django-locallibrary-tutorial/templates/registration/password_reset_done.html**, and give it the following contents: ```django {% extends "base_generic.html" %} @@ -337,7 +338,7 @@ This form is displayed after your email address has been collected. Create **/lo #### Password reset email -This template provides the text of the HTML email containing the reset link that we will send to users. Create **/locallibrary/templates/registration/password_reset_email.html**, and give it the following contents: +This template provides the text of the HTML email containing the reset link that we will send to users. Create **/django-locallibrary-tutorial/templates/registration/password_reset_email.html**, and give it the following contents: ```django Someone asked for password reset for email \{{ email }}. Follow the link below: @@ -346,7 +347,7 @@ Someone asked for password reset for email \{{ email }}. Follow the link below: #### Password reset confirm -This page is where you enter your new password after clicking the link in the password reset email. Create **/locallibrary/templates/registration/password_reset_confirm.html**, and give it the following contents: +This page is where you enter your new password after clicking the link in the password reset email. Create **/django-locallibrary-tutorial/templates/registration/password_reset_confirm.html**, and give it the following contents: ```django {% extends "base_generic.html" %} @@ -382,7 +383,7 @@ This page is where you enter your new password after clicking the link in the pa #### Password reset complete -This is the last password-reset template, which is displayed to notify you when the password reset has succeeded. Create **/locallibrary/templates/registration/password_reset_complete.html**, and give it the following contents: +This is the last password-reset template, which is displayed to notify you when the password reset has succeeded. Create **/django-locallibrary-tutorial/templates/registration/password_reset_complete.html**, and give it the following contents: ```django {% extends "base_generic.html" %} @@ -395,22 +396,20 @@ This is the last password-reset template, which is displayed to notify you when ### Testing the new authentication pages -Now that you've added the URL configuration and created all these templates, the authentication pages should now just work! - -You can test the new authentication pages by attempting to log in to and then log out of your superuser account using these URLs: - -- `http://127.0.0.1:8000/accounts/login/` -- `http://127.0.0.1:8000/accounts/logout/` +Now that you've added the URL configuration and created all these templates, the authentication pages (other than logout) should now just work! +You can test the new authentication pages by first attempting to log in to your superuser account using the URL `http://127.0.0.1:8000/accounts/login/`. You'll be able to test the password reset functionality from the link in the login page. **Be aware that Django will only send reset emails to addresses (users) that are already stored in its database!** +Note that you won't be able to test account logout yet, because logout requests must be sent as a `POST` rather than a `GET` request. + > **Note:** The password reset system requires that your website supports email, which is beyond the scope of this article, so this part **won't work yet**. To allow testing, put the following line at the end of your settings.py file. This logs any emails sent to the console (so you can copy the password reset link from the console). > > ```python > EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' > ``` > -> For more information, see [Sending email](https://docs.djangoproject.com/en/4.2/topics/email/) (Django docs). +> For more information, see [Sending email](https://docs.djangoproject.com/en/5.0/topics/email/) (Django docs). ## Testing against authenticated users @@ -422,27 +421,46 @@ You can get information about the currently logged in user in templates with the Typically you will first test against the `\{{ user.is_authenticated }}` template variable to determine whether the user is eligible to see specific content. To demonstrate this, next we'll update our sidebar to display a "Login" link if the user is logged out, and a "Logout" link if they are logged in. -Open the base template (**/locallibrary/catalog/templates/base_generic.html**) and copy the following text into the `sidebar` block, immediately before the `endblock` template tag. +Open the base template (**/django-locallibrary-tutorial/catalog/templates/base_generic.html**) and copy the following text into the `sidebar` block, immediately before the `endblock` template tag. ```django ``` As you can see, we use `if` / `else` / `endif` template tags to conditionally display text based on whether `\{{ user.is_authenticated }}` is true. If the user is authenticated then we know that we have a valid user, so we call `\{{ user.get_username }}` to display their name. -We create the login and logout link URLs using the `url` template tag and the names of the respective URL configurations. Note also how we have appended `?next=\{{ request.path }}` to the end of the URLs. What this does is add a URL parameter `next` containing the address (URL) of the _current_ page, to the end of the linked URL. After the user has successfully logged in/out, the views will use this "`next`" value to redirect the user back to the page where they first clicked the login/logout link. +We create the login link URL using the `url` template tag and the name of the `login` URL configuration. Note also how we have appended `?next=\{{ request.path }}` to the end of the URL. What this does is add a URL parameter `next` containing the address (URL) of the _current_ page, to the end of the linked URL. After the user has successfully logged in, the view will use this "`next`" value to redirect the user back to the page where they first clicked the login link. + +The logout template code is different, because from Django 5 to logout you must `POST` to the `admin:logout` URL, using a form with a button. +By default this would render as a button, but you can style the button to display as a link. +For this example we're using _Bootstrap_, so we make the button look like a link by applying `class="btn btn-link"`. +YOu also also need to append the following styles to **/django-locallibrary-tutorial/catalog/static/css/styles.css** in order to correctly position the logout link next to all the other sidebar links: + +```css +#logout-form { + display: inline; +} +#logout-form button { + padding: 0; + margin: 0; +} +``` -> **Note:** Try it out! If you're on the home page and you click Login/Logout in the sidebar, then after the operation completes you should end up back on the same page. +Try it out by clicking the Login/Logout links in the sidebar. ### Testing in views @@ -475,7 +493,7 @@ class MyView(LoginRequiredMixin, View): redirect_field_name = 'redirect_to' ``` -For additional detail, check out the [Django docs here](https://docs.djangoproject.com/en/4.2/topics/auth/default/#limiting-access-to-logged-in-users). +For additional detail, check out the [Django docs here](https://docs.djangoproject.com/en/5.0/topics/auth/default/#limiting-access-to-logged-in-users). ## Example — listing the current user's books @@ -494,7 +512,7 @@ from django.conf import settings ``` Next, add the `borrower` field to the `BookInstance` model, setting the user model for the key as the value of the setting `AUTH_USER_MODEL`. -Since we have not overridden the setting with a [custom user model](https://docs.djangoproject.com/en/4.2/topics/auth/customizing/) this maps to the default `User` model from `django.contrib.auth.models`. +Since we have not overridden the setting with a [custom user model](https://docs.djangoproject.com/en/5.0/topics/auth/customizing/) this maps to the default `User` model from `django.contrib.auth.models`. ```python borrower = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True) @@ -638,7 +656,7 @@ When the development server is running, you should now be able to view the list The very last step is to add a link for this new page into the sidebar. We'll put this in the same section where we display other information for the logged in user. -Open the base template (**/locallibrary/catalog/templates/base_generic.html**) and add the "My Borrowed" line to the sidebar in the position shown below. +Open the base template (**/django-locallibrary-tutorial/catalog/templates/base_generic.html**) and add the "My Borrowed" line to the sidebar in the position shown below. ```django