본문 바로가기
DoItDJango

Chapter 10: Bootstrap

by 자동매매 2023. 4. 17.

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

Shape47

(news) $ python manage.py startapp pages

Shape48

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

Shape49

  • 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

]

Shape50

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

Shape51

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

]

Shape52

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

Shape53

(news) $ touch pages/urls.py

Shape54

Then import our not-yet-created views, set the route paths, and make sure to name each url, too.

Chapter 10: Bootstrap 196

Code

Shape55

pages/urls.py

from django.urls import path

from .views import HomePageView

urlpatterns = [

path('', HomePageView.as_view(), name='home'),

]

Shape56

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

Shape57

pages/views.py

from django.views.generic import TemplateView

class HomePageView (TemplateView):

template_name = 'home.html'

Shape58

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

Shape59

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)

Shape60

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

Shape61

(news) $ python manage.py test

Shape62

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

Shape63

<!-- 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 >

Shape64

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

Shape65

<!-- 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 >

Shape66

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

Shape67

<!-- templates/registration/login.html -->

...

< button class="btn btn-success ml-2" type="submit">Log In</ button >

...

Shape68

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

Shape69

(news) $ pipenv install django-crispy-forms==1.8.1

Shape70

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

Shape71

  • 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',

]

Shape72

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

Shape73

  • newspaper_project/settings.py CRISPY_TEMPLATE_PACK = 'bootstrap4'

Shape74

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

Shape75

<!-- 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 %}

Shape76

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

Shape77

<!-- templates/signup.html -->

...

< button class="btn btn-success" type="submit">Sign Up</ button >

...

Shape78

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

댓글