Chapter 10: Bootstrap
Web development requires a lot of skills. Not only do you have to program the website to work correctly, users expect it to look good, too. When you're creating everything from scratch, it can be overwhelming to also add all the necessary HTML/CSS for a beautiful site.
Fortunately there'sBootstrap, the most popular framework for building responsive, mobile-first projects. Rather than write all our own CSS and JavaScript for common website layout features, we can instead rely on Bootstrap to do the heavy lifting. This means with only a small amount of code on our part we can quickly have great looking websites. And if we want to make custom changes as a project progresses, it's easy to override Bootstrap where needed, too.
When you want to focus on the functionality of a project and not the design, Bootstrap is a great choice. That's why we'll use it here.
Pages App
In the previous chapter we displayed our homepage by including view logic in our urls.py file. While this approach works, it feels somewhat hackish to me and itcertainly doesn't scale as a website grows over time. It is also probably somewhat confusing to Django newcomers. Instead we can and should create a dedicated pages app for all our static pages. This will keep our code nice and organized going forward.
On the command line use the startapp command to create our new pages app. If the server is still running you may need to type Control+c first to quit it.
Chapter 10: Bootstrap | 194 |
---|
Command Line
(news) $ python manage.py startapp pages
Then immediately update our settings.py file. I often forget to do this so it is a good practice to just think of creating a new app as a two-step process: run the startapp command then update INSTALLED_APPS.
Code
- newspaper_project/settings.py
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'users.apps.UsersConfig', 'pages.apps.PagesConfig', # new
]
Now we can update our urls.py file inside the newspaper_project directory. Go ahead and remove the import of TemplateView. We will also update the '' route to include the pages app.
Chapter 10: Bootstrap | 195 |
---|
Code
newspaper_project/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('users/', include('users.urls')),
path('users/', include('django.contrib.auth.urls')), path('', include('pages.urls')), # new
]
It's time to add our homepage which means Django's standard urls/views/templates dance. We'll start with the pages/urls.py file. First create it.
Command Line
(news) $ touch pages/urls.py
Then import our not-yet-created views, set the route paths, and make sure to name each url, too.
Chapter 10: Bootstrap | 196 |
---|
Code
pages/urls.py
from django.urls import path
from .views import HomePageView
urlpatterns = [
path('', HomePageView.as_view(), name='home'),
]
The views.py code should look familiar at this point. We're using Django's TemplateView generic class-based view which means we only need to specify our template_name to use it.
Code
pages/views.py
from django.views.generic import TemplateView
class HomePageView (TemplateView):
template_name = 'home.html'
We already have an existing home.html template. Let's confirm it still works as expected with our new url and view. Start up the local server python manage.py runserver and navigate to the homepage athttp://127.0.0.1:8000/to confirm itremains unchanged.
Chapter 10: Bootstrap | 197 |
---|
Homepage logged in
It should show the name of your logged in superuser account which we used at the end of the last chapter.
Tests
We've added new code and functionality which means it's time for tests. You can never have enough tests in your projects. Even though they take some upfront time to write, they always save you time down the road and give confidence as a project grows in complexity.
There are two ideal times to add tests: either before you write any code (test-driven-development) or immediately after you've added new functionality and it's clear in your mind.
Currently, our project has four pages:
home
sign up
log in
log out
However we only need to test the first two. Log in and log out are part of Django and rely on internal views and url routes. They therefore already have test coverage. If we made substantial changes to them in the future, we would want to add tests for that. But as a general rule, you do not need to add tests for core Django functionality.
Chapter 10: Bootstrap | 198 |
---|
Since we have urls, templates, and views for each of our two new pages we'll add tests for each. Django'sSimpleTestCasewill suffice for testing the homepage but the sign up page uses the database so we'll need to useTestCasetoo.
Here's what the code should look like in your pages/tests.py file.
Code
pages/tests.py
from django.contrib.auth import get_user_model from django.test import SimpleTestCase, TestCase from django.urls import reverse
class HomePageTests (SimpleTestCase):
def test_home_page_status_code(self):
response = self.client.get('/')
self.assertEqual(response.status_code, 200)
def test_view_url_by_name(self):
response = self.client.get(reverse('home'))
self.assertEqual(response.status_code, 200)
def test_view_uses_correct_template(self): response = self.client.get(reverse('home')) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'home.html')
class SignupPageTests (TestCase):
Chapter 10: Bootstrap | 199 |
---|
username = 'newuser'
email = 'newuser@email.com'
def test_signup_page_status_code(self):
response = self.client.get('/users/signup/')
self.assertEqual(response.status_code, 200)
def test_view_url_by_name(self):
response = self.client.get(reverse('signup'))
self.assertEqual(response.status_code, 200)
def test_view_uses_correct_template(self):
response = self.client.get(reverse('signup'))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'signup.html')
def test_signup_form(self):
new_user = get_user_model().objects.create_user( self.username, self.email)
self.assertEqual(get_user_model().objects.all().count(), 1)
self.assertEqual(get_user_model().objects.all()
[0].username, self.username)
self.assertEqual(get_user_model().objects.all()
[0].email, self.email)
On the top line we useget_user_model()to reference our custom user model. Then for both pages we test three things:
Chapter 10: Bootstrap | 200 |
---|
the page exists and returns a HTTP 200 status code
the page uses the correct url name in the view
the proper template is being used
Our sign up page also has a form so we should test that, too. In the test test_signup_-form we're verifying that when a username and email address are POSTed (sent to thedatabase), they match what is stored on the CustomUser model.
Note that there are two ways to specify a page: either hardcoded as in test_signup_-page_status_code where we set the response to /users/signup/ or via the URL nameof signup which is done for test_view_url_by_name and test_view_uses_correct_-template.
Quit the local server with Control+c and then run our tests to confirm everything passes.
Command Line
(news) $ python manage.py test
Bootstrap
If you've never used Bootstrap before you're in for a real treat. It accomplishes so much in so little code.
There are two ways to add Bootstrap to a project: you can download all the files and serve them locally or rely on a Content Delivery Network (CDN). The second approach is simpler to implement provided you have a consistent internet connection so that's what we'll use here.
Bootstrap comes with a starter templatethat includes the basic files needed. Notablythere are four that we incorporate:
Chapter 10: Bootstrap | 201 |
---|
Bootstrap.css
jQuery.js
Popper.js
Bootstrap.js
Here's what the updated base.html file should look like. Generally you should type all code examples yourself but as this is one is quite long and error-prone, it's recommended to copy and paste fromthe official source code.
Code
<!-- templates/base.html -->
<!doctype html>
< html lang="en">
< head >
<!-- Required meta tags -->
< meta charset="utf-8">
< meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
< link rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/\
bootstrap.min.css"
integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81i\
uXoPkFOJwJ8ERdknLPMO"
crossorigin="anonymous">
< title >Hello, world!</ title >
</ head >
< body >
Chapter 10: Bootstrap | 202 |
---|
< h1 >Hello, world!</ h1 >
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
< script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4\
YfRvH+8abtTE1Pi6jizo"
crossorigin="anonymous"></ script >
< script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/\
1.14.3/
umd/popper.min.js"
integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbB\
JiSnjAK/
l8WvCWPIPm49"
crossorigin="anonymous"></ script >
< script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/\
js/bootstrap.min.js"
integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ\
6OW/JmZQ5stwEULTy"
crossorigin="anonymous"></ script >
</ body >
</ html >
If you start the server again with python manage.py runserver and refresh the homepage athttp://127.0.0.1:8000/you'll see that only the font size has changed at the moment.
Chapter 10: Bootstrap | 203 |
---|
Homepage with Bootstrap
Let's add a navigation bar at the top of the page which contains our links for the homepage, log in, log out, and sign up. Notably we can use theif/elsetags in the Django templating engine to add some basic logic. We want to show a "log in" and "sign up" button to users who are logged out, but a "log out" and "change password" button to users logged in.
Here's what the code looks like. Again, it's ok to copy/paste here since the focus of this book is on learning Django not HTML, CSS, and Bootstrap.
Code
<!-- templates/base.html -->
<!doctype html>
< html lang="en">
< head >
<!-- Required meta tags -->
< meta charset="utf-8">
< meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
< link rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/\
bootstrap.min.css"
integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81i\
uXoPkFOJwJ8ERdknLPMO"
Chapter 10: Bootstrap204
crossorigin="anonymous">
< title >{% block title %}Newspaper App{% endblock title %}</ title >
</ head >
< body >
< nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4"> < a class="navbar-brand" href="{% url 'home' %}">Newspaper</ a >
< button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
< span class="navbar-toggler-icon"></ span > </ button >
< div class="collapse navbar-collapse" id="navbarCollapse"> {% if user.is_authenticated %}
< ul class="navbar-nav ml-auto">
< li class="nav-item">
< a class="nav-link dropdown-toggle" href="#" id="userMenu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{ user.username }} </ a >
< div class="dropdown-menu dropdown-menu-right" aria-labelledby="userMenu">
< a class="dropdown-item"
href="{% url 'password_change'%}">Change password</ a > < div class="dropdown-divider"></ div >
< a class="dropdown-item" href="{% url 'logout' %}"> Log Out</ a >
</ div >
Chapter 10: Bootstrap205
</ li >
</ ul >
{% else %}
< form class="form-inline ml-auto">
< a href="{% url 'login' %}" class="btn btn-outline-secondary"> Log In</ a >
< a href="{% url 'signup' %}" class="btn btn-primary ml-2"> Sign up</ a >
</ form >
{% endif %}
</ div >
</ nav >
< div class="container">
{% block content %}
{% endblock content %}
</ div >
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
< script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4\
YfRvH+8abtTE1Pi6jizo"
crossorigin="anonymous"></ script >
< script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/\
1.14.3/
umd/popper.min.js"
integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbB\
JiSnjAK/
l8WvCWPIPm49"
Chapter 10: Bootstrap | 206 |
---|
crossorigin="anonymous"></ script >
< script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/\
js/bootstrap.min.js"
integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ\
6OW/JmZQ5stwEULTy"
crossorigin="anonymous"></ script >
</ body >
</ html >
If you refresh the homepage athttp://127.0.0.1:8000/our new nav has magically appeared! We've also added in our {% block content %} tags so the user greeting has returned, as has our "Newspaper App" in the title.
Homepage with Bootstrap nav logged in
Click on the username in the upper right hand corner–wsv in my case–to see the nice dropdown menu Bootstrap provides.
Homepage with Bootstrap nav logged in and dropdown
If you click on the "Log Out" link then our nav bar changes offering links to either "Log
Chapter 10: Bootstrap | 207 |
---|
In" or "Sign Up."
Homepage with Bootstrap nav logged out
Better yet if you shrink the size of your browser window Bootstrap automatically resizes and makes adjustments so it looks good on a mobile device, too.
Homepage mobile with hamburger icon
You can even change the width of the web browser to see how the side margins change as the screen size increases and decreases.
If you click on the "Log Out" button and then "Log In" from the top nav you can also see that our log in pagehttp://127.0.0.1:8000/users/loginlooks better too.
Chapter 10: Bootstrap | 208 |
---|
Bootstrap login
The only thing that looks off is our "Login" button. We can use Bootstrap to add some nice styling such as making it green and inviting.
Change the "button" line in templates/registration/login.html as follows.
Code
<!-- templates/registration/login.html -->
...
< button class="btn btn-success ml-2" type="submit">Log In</ button >
...
Now refresh the page to see our new button.
Chapter 10: Bootstrap | 209 |
---|
Bootstrap log in with new button
Sign Up Form
Our sign up page athttp://127.0.0.1:8000/users/signup/has Bootstrap stylings but also distracting helper text. For example after "Username" it says "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
Chapter 10: Bootstrap | 210 |
---|
Updated navbar logged out
Where did that text come from, right? Whenever something feels like "magic" in Django rest assured that it is decidedly not. Likely the code came from an internal piece of Django.
The fastest method I've found to figure out what's happening under-the-hood in Django is to simply go to theDjango source code on Github, use the search bar and try to find the specific piece of text.
For example, if you do a search for "150 characters or fewer" you'll find yourself on the django/contrib/auth/models.py pagelocated hereon line 301. The text comes as part of the auth app, on the username field for AbstractUser.
We have three options now:
override the existing help_text
hide the help_text
Chapter 10: Bootstrap | 211 |
---|
• restyle the help_text
We'll choose the third option since it's a good way to introduce the excellent 3rd party packagedjango-crispy-forms.
Working with forms is a challenge and django-crispy-forms makes it easier to write DRY code.
First, stop the local server with Control+c. Then use Pipenv to install the package in our project.
Command Line
(news) $ pipenv install django-crispy-forms==1.8.1
Add the new app to our INSTALLED_APPS list in the settings.py file. As the number of apps starts to grow, I find it helpful to distinguish between 3rd party apps and local apps I've added myself. Here's what the code looks like now.
Code
- newspaper_project/settings.py
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles',
3rd Party
'crispy_forms', # new
Chapter 10: Bootstrap | 212 |
---|
- Local 'users.apps.UsersConfig', 'pages.apps.PagesConfig',
]
Since we're using Bootstrap4 we should also add that config to our settings.py file.
This goes on the bottom of the file.
Code
- newspaper_project/settings.py CRISPY_TEMPLATE_PACK = 'bootstrap4'
Now in our signup.html template we can quickly use crispy forms. First, we load crispy_forms_tags at the top and then swap out {{ form.as_p }} for {{ form|crispy }}.
Code
<!-- templates/signup.html -->
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block title %}Sign Up{% endblock title%}
{% block content %}
< h2 >Sign up</ h2 >
< form method="post">
{% csrf_token %}
{{ form|crispy }}
< button type="submit">Sign Up</ button >
Chapter 10: Bootstrap | 213 |
---|
</ form >
{% endblock content %}
If you start up the server again with python manage.py runserver and refresh the sign up page we can see the new changes.
Crispy sign up page
Much better. Although how about if our "Sign Up" button was a little more inviting? Maybe make it green? Bootstrap hasall sorts of button styling optionswe can choose from. Let's use the "success" one which has a green background and white text.
Update the signup.html file on the line for the sign up button.
Chapter 10: Bootstrap | 214 |
---|
Code
<!-- templates/signup.html -->
...
< button class="btn btn-success" type="submit">Sign Up</ button >
...
Refresh the page and you can see our updated work.
Crispy sign up page green button
Chapter 10: Bootstrap | 215 |
---|
Conclusion
Our Newspaper app is starting to look pretty good. The last step of our user auth flow is to configure password change and reset. Here again Django has taken care of the heavy lifting for us so it requires a minimal amount of code on our part.
'DoItDJango' 카테고리의 다른 글
aws 호스팅1 (0) | 2023.04.28 |
---|---|
sendgrid를 통한 메일 전송 (0) | 2023.04.18 |
User Authentication (0) | 2023.04.17 |
Django db.sqlite3 초기화 (0) | 2023.04.16 |
User Accounts (0) | 2023.04.16 |
댓글