|
|
16:53 |
|
show
|
1:17 |
Welcome to getting started with Django.
My name is Christopher and I will be your guide through the wonderful world of Django web programming.
This introductory chapter will give you an overview of the topics covered in this course, namely Django.
I'll start out by describing what you'll need to know before coding.
Along with me then we'll talk about each of the major parts of Django.
The first part is the URL routing mechanism.
This is what translates the web address your Django project receives into a call to some code that returns HTML.
That code I just mentioned, Django calls it a view, views get called when a URL is hit and are responsible for returning a document.
Usually HTML.
Django comes with a template engine for producing HTML, giving you the power of re use that you are accustomed to encoding but applied to text based documents.
Django also interfaces with the database.
The database is abstracted away through what Django calls models.
These models allow you to think of objects rather than rows in a database.
And finally, Django comes with all sorts of the tools you need when writing a complex website tools you might otherwise have to write for yourself creating and managing users for example or administering that database I just mentioned
|
|
show
|
1:49 |
Django is a whole lot of stuff.
In fact, it's probably one of the bigger frameworks out there for Python.
It is this big because it's opinionated.
This can be both a good and a bad thing.
Most of what you want to do when writing a project for the web is built in here, meaning you have less code to write the bad part.
Well, trying to do something that isn't quite the Django way sometimes requires a bit extra effort.
That's the tradeoff.
Some key features of Django are an interface built loosely around the model view controller concept This means there's a separation between the data, the business logic and the presentation of the results.
Web pages live at addresses and Django provides a routing mechanism to map between those addresses and your presentation logic.
This includes tools for managing parameters in your URL in two different ways and ultimately for a web project, you're trying to spit out some HTML.
HTML can be very repetitive.
Each page having the same headers and footers is actually quite common.
The Django template engine gives you a way of creating reusable composable pieces, allowing you to write HTML in a fashion similar to how you write code.
The model part of model view controller is an abstraction for data.
As soon as you have data in a database, you tend to need a whole bunch of code to manage it, creating it, updating it, deleting it, etc.
Django comes with a management suite called the Django admin that provides a low code library for managing all your data objects.
Lots of websites need user accounts.
Users are just another kind of data, but they have special requirements, account creation, password resets, permissions, loads of things you need to do, Django helps you out here by providing libraries and stubs for managing users, their authentication, and their authorization in your sight.
|
|
show
|
1:06 |
There's lots of frameworks out there.
Why pick Django?
Personally, I find the interface just makes sense.
The libraries are pretty clean.
The pieces fit together nicely and the framework's pretty robust Most of your website needs can be found all in this one place.
This not only means you don't have to look elsewhere, it also means you're guaranteed everything will work together.
Django scales up pretty well as well.
If your next project becomes a huge success, you'll be happy to know you can still use Django with it.
This is partially due to its share nothing design.
There are very few dependencies between pieces, which means you can split things up into multiple servers to achieve your performance needs.
Security is one of those things you don't want to have to think about.
You want experts to think about it for you.
Django provides tools for authentication, authorization and permissions allowing you to keep the private parts of your site well.
Private.
And of course, it's open source.
It uses a BSD license, which means it plays well with other kinds of copyrights and you can safely use it for private enterprises.
|
|
show
|
0:37 |
It's always nice to be in good company.
I suspect you recognize some of these logos.
Django has been around since 2003 and was originally built by Web programmers at the Lawrence Journal World Newspaper in the city of Lawrence Kansas in the United States.
They built the framework to help their team build their newspaper's site.
Five years later, after much success, the Django Software Foundation was created to manage the library and its future work.
Some of these sites use Django for internal tools and some for their entire site.
I believe I mentioned earlier that scaling is something Django is good at.
You can probably see that here.
|
|
show
|
0:43 |
What will you need to know if you're going to coat along with me?
For starters, this isn't an introduction to Python course.
Django code probably shouldn't be your first Python code.
You should have some familiarity with flow control structures like loops and conditionals data structures such as lists, dictionaries and tuples and package management.
Knowing how to use pip and virtual environments to install third party packages.
You'll also need some understanding of HTML.
I'll be using some bootstrap to make the pages a bit prettier but not in a hardcore way.
And similarly, you'll need to know a little bit of CSS.
The course focuses on Django, but it's hard to demo web framework without some web stuff showing up along the way.
|
|
show
|
0:24 |
All the code examples here were written using Python 3.
10 and Django 4.0.
Very little of what I'm showing is new to either of these versions.
So if you're using something a bit older, you're probably okay.
I do tend to use f strings which were introduced in Python 3.
6 and enough has changed in Django that you probably don't want to go earlier than 3.0, but otherwise you'll probably be good.
|
|
show
|
2:08 |
You're sitting at home and you suddenly have a desire to look up your next good read.
You type in the URL of your favorite Django based book site and you get back a list of books.
Just how does that happen?
Well, let's take a look.
Your browser looks at the host name part of the URL And uses that to contact a server.
That server sees the whole address and uses it to make a decision.
In the case of Django a routing manager looks at the address, particularly the stuff after the host name and makes a decision based on a giant URL map.
That map points to some code or if it doesn't points to some code that handles what to do.
If the URL isn't found.
This code in Django is called a view.
When I write a view, I tend to call it something very close to the URL.
It doesn't have to be but it makes finding stuff easier So the book listing part of the URL maps to the book listing view where some code is run.
That code creates a list of books from the database.
Then builds a data context which is a special dictionary, uses that context to call a template engine that is responsible for rendering a template.
In this case some HTML.
That rendering would include things like looping through all the books that are in the context and putting them inside of an HTML unordered list The template engine returns the result to the view.
The view wraps the result in an HTTP-response object, the kind of object Django expects all views to return and then returns it.
Django takes that response object and forms the body of the HTTP-response, passing it down to the Web server, and the web server then returns the whole thing to your browser.
For quick sites all this happens in under a second and you get to choose which book you're going to read next.
Let's talk a little more about each one of these pieces and what Django does for you.
|
|
show
|
2:32 |
So you've seen how the entry point to Django is the URL mapping.
What exactly is this?
It's a Python list that contains references to routes.
There are several different ways of expressing a URL match.
Ranging from simple text to more complicated regular expressions.
Each route you specify can map to a view function that returns an HTTPResponse object or if you'd rather something more object oriented, there is a way of building views with classes as well.
Although personally I prefer the functions and you can also map to another mapping.
This works well if you've got a self contained module you can nest all that module's URLs under a root URL If you like hierarchical organization for the win.
URLs map to views.
A view is where your business logic goes and is responsible for returning some content.
That content is expressed as an HTTPResponse object.
There are other kinds of objects as well, but this course will only use this one.
A view can take arguments.
Django prefers to place arguments inside the URL rather than using query parameters.
It supports the latter, but the former maps nicely like I mentioned before.
I tend to name my views the same thing as my URL So the joint group URL That needs two parameters specifies them as part of the URL itself and Django will map that to the arguments in your joint group view.
You'll notice that these arguments are the 2nd and 3rd to the view.
All views take a request object that gives information about the http request as the first argument.
Views can be either functions or classes.
The function style tends to be a bit more verbose but also tends to be a bit easier to read.
It explicitly tells you what is happening.
The class style wraps something so that you don't have to write as much code, but you have to remember what magic things the class does for you.
Typically each view is going to spit out some HTML.
It does this by using the template engine to create some content.
This is so common there is a library full of shortcuts that encapsulates this process in Django and of course you might not need to write your own view either.
Django ships with some.
That may mean you have less code to write.
One example covered in a future chapter is to do a redirect.
This can be done in a single line because Django provides you a pre canned view for exactly this purpose.
|
|
show
|
1:30 |
The original writers of the Django framework were coders working at a newspaper.
They had a strong desire to separate out the business logic from the presentation, particularly because they had an HTML design staff.
They wanted it to be simple for those design folks to do some coding like things but without having to know how to write Python.
A big part of this is the template rendering engine.
It isn't HTML specific, but a generic mechanism for template text.
HTML is the typical use, but you may also use it for formatting an email the system is sending out or any other text presentation you might want to do.
The template engine has its own mini language comprised of tags and filters.
The tags are placeholders that are replaced with content and depending on the tag, do different things.
Out of the box Django comes with control flow tags that allow you to do four loops like repetition and if statement like conditionals.
You can also reuse your content by creating blocks, inheriting them or including them in the document.
Or you can render data sent into the template engine as variables, through the data context.
The other part of the mini language is filters.
Filters work inside of tags that render variables and act as data presentation modifiers.
They change how the variable is rendered.
For example, the filter might take a date object and present it as the appropriately localized shortform for a date.
|
|
show
|
1:02 |
If your website is going to be more than just static pages, you're likely to need to manage some data, Djangos models, abstract away a database.
To create a model, you define classes with fields that define what should be contained in the object and then Django maps this to tables in the database.
Several different databases are supported out of the box, and even more are available as 3rd party plug ins.
As your project changes, your models may change.
This means that the underlying database has to change as well.
That can get complicated.
Django provides a migration mechanism that helps you modify the database as your code evolves.
Models are also able to specify relationships between each other.
This maps to things like foreign keys in the database and of course storing isn't enough you have to get the data back out.
Django provides a query manager for each class.
You define, allowing you to execute queries on the database all the while mapping it to your objects.
|
|
show
|
0:31 |
Users are just data, but their data with a whole bunch of extra things you have to do.
Thankfully, Django provides tools for you to take care of the most common user management tasks.
It provides a registration framework for signing users up and managing their passwords, an authentication framework for dealing with users logging into and out of your site, an authorization framework to determine what a user is allowed to see and provides built in decorators, so all you need to ensure a view is secure is a single line of code.
|
|
show
|
0:48 |
All this data, users included is going to need managing.
Almost all data has to go through the credit operations.
That's create, read, update, and delete.
CRUD it's just fun to say, since this is so common, why I have to write all this yourself every single time.
You might have guessed.
I'm going to tell you you don't have to.
Django comes with the Django admin, a tool that allows you to perform all the common crowd operations on any model class you define.
It requires a few extra lines of code, but it takes advantage of the model framework to minimize how much you have to do.
By default, this toolset is restricted to a subset of your users, staff members and super users, you can change that if you want to open it up to others.
But the typical use here is for the admins of your site
|
|
show
|
0:58 |
If all this goodness isn't enough.
The framework is designed using applicable architecture.
The same techniques the maintainers used to organize Django are used by you and 3rd party writers This makes the framework quite extensible.
One small bit of unfortunateness though, they could have chosen some of their terminology a little bit better.
There are two terms you'll need to know.
A Django project is the software, you're writing the website.
A Django app is a module.
This can get confusing as the term application usually means the whole thing, but in this case it means a module.
Apps can be shipped and managed separately.
This is part of that whole pluggable thing.
This pluggability names being problematic notwithstanding, is how a huge community of people have gotten together to write 3rd party apps.
You can take advantage a lot of them, which means less code for you to write.
Stand on some giants shoulders.
|
|
show
|
1:28 |
This course is divided up into a series of chapters.
Each chapter covers a different area of the Django framework.
Along the way, you'll be building a Django project, incrementally approving upon it as you learn new things.
The next chapter walks you through setting up your initial Django project, you'll be coding almost right away.
Then I'll talk about the templating engine and then dive into models, the databases and queries.
with models in place you'll see how to take care of those CRUD operations inside of the Django admin and then it's off to user land, registering, managing and dealing with all that passwordy stuff.
The next two chapters are about getting content from your users first in the way of forms and then in the way of uploads.
Django provides a series of command line utilities to manage your project, and true to its pluggable nature allows you to write your own and as your project grows, your models will change.
You'll learn how to manage this and how it impacts the database through running migrations.
With the core concepts out of the way.
I'll spend a little bit of time talking about what it means to deploy all this stuff in production and then finally, I'll give you a tour of some of the more popular third party libraries out there and what they can do for you.
Well, this is exciting.
You're all set to go.
Next up, I'll show you how to set everything up and create your first Django project
|
|
|
25:08 |
|
show
|
1:33 |
Alright, let's get going with your first Django project.
This chapter will introduce you to the main concepts of Django.
I'll start by showing you how to use pip to install the Django framework, then you'll use the Django admin command that comes with the framework to create a new project with your very own Django project in place, you'll use its built in development server to see the default content.
Then you'll write some code.
I'm a traditionalist.
Where better to start than with Hello World, and of course it is considered good practice to write tests whenever you code.
Every chapter in this course will include a section on testing, showing you just how to write automated tests that goes with what you've learned.
This chapter will highlight the key testing concepts in Django and how they're built on top of Pythons Unit Test Library.
Django has been around for more than 15 years now and as you might imagine with that kind of history, there are lots of versions out there.
Each version of Django is tied to a number of Python versions.
I'm going to be demonstrating using Python 3.10 and Django 4.0.2.
Very little of what I'm going to be showing you will need these versions.
So if you're on a slightly older build, that shouldn't be a problem.
Django 4.0's minimum, Python version is Python 3.8.
So if you're going to use Django 4.0 along with me, you'll need at least Python 3.8.
You can probably get away with a version three of Django without seeing any differences.
But I'll be using 4.0, and it's always a good idea when you're installing third party libraries in Python to use a virtual environment.
Before continuing, you should set one up for yourself now.
|
|
show
|
3:34 |
Here I'm in my trusty terminal with a virtual environment activated, like most third party libraries.
Django is available on PyPi, so I'm going to use pip to install it and there it is.
Django 4.0.2.
with a few dependencies.
packaged with Django is the Django admin command.
It is now available on your execution path.
Let me run it here.
Well, that was a lot, wasn't it?
Let me just scroll this back up.
The Django Admin command doesn't do anything on its own besides print out help message to get something useful done.
You need to use a subcommand as I didn't use a subcommand, Django admin helpfully prints out all the possible choices I could use.
In a future lesson, I'll show you how to write your own subcommands and when you do they'll appear here as well.
The particular command I'm interested in right now is startproject.
That's the one that creates a new project.
A project is where your code lives and will be the thing that outputs your web content to your users.
You might note that I'm being cagey here and I'm specifically not using the word application, even though that would make sense.
You'll see why I don't want to use that word in a minute.
Let's run the startproject command creating a new project named Alexandria after the famous historical library.
I'll just scroll back down here and run the command.
The Django Admin command doesn't tell you anything once it has created the project, it just returns but if I look in my directory.
If you're a Windows person ls is the eunuchs equivalent of DIR.
It lists the contents of a directory.
The start project subcommand creates the project directory using the name you passed in.
In this case, Alexandria.
A bunch of files that define the base structure for the project get created for you.
Let's take a look at them.
You may or may not have the tree command on your system.
Don't worry about it.
All it does is list the contents of a directory.
Inside of Alexandria there are two things.
First confusingly, another directory named Alexandria.
I have never understood why the good folks at Django decided to use this double naming structure instead of calling it something like system, but that's how it is.
The inner Alexandria directory has five files in it which are key to running a Django server, the ASGI and WSGI files are hooks for connecting Django to a web server such as Apache or nginx.
WSGI is a standard for this kind of interconnection and is short for web server gateway interface.
ASGI is the asynchronous equivalent.
Unless you're doing something a bit more advanced you won't have to touch either of these files.
The other two files here will become like old friends by the time you're done with this course.
Settings.py, contains configuration for your web service, there are over 100 supported configuration settings in Django and more get added when you install third party applications.
The start project command sets up the mandatory settings and has some reasonable defaults.
The URLs file maps, URLs typed in the browser to the code that returns HTML for the browser to display.
You'll see more on this shortly.
The outer alexandria directory has two things in it, the inner alexandria directory and a manage.py file.
The manage.py file is a local version of the Django admin with context specific subcommands.
You'll be using this to run a development server very soon.
|
|
show
|
1:32 |
Well, that's the grand tour of what gets created in the project.
Let's go inside of the project.
I'm now in the project directory and now I will run the development server.
I run the development server by using the runserver.
subcommand on the managed py copy of the Django admin.
Running this does some checks on your project, tells you what settings file is being used and what URL you can hit to use the server.
By default Django hosts at local host port 8000.
Throughout this course you'll be using this development server It is your best bet for debugging your code and getting your web content looking the way you like.
Note though, this is a development server.
It has not been hardened and in fact leaks all sorts of information that you wouldn't want on the internet.
This same stuff is helpful for debugging but could be considered a security issue.
If you're going to host something in public, you must not use this development server but hook your Django code up to something like Apache or Nginx.
Details on how to do this are outside the scope of this course.
Alright with that warning aside, let's visit the URL By default, Django shows you this rocket ship as you haven't written any other code yet.
This is just here to show you the server is working.
It's a pretty rocketship, but get on top of replacing it with something else.
|
|
show
|
3:44 |
You may recall that.
I was careful about avoiding the word application earlier.
The reason for this is Django divides the world into two things.
Projects and apps.
A project is your web server stuff.
An app is a module inside of that project.
To avoid confusion, I try not to use the word application while talking about Django unless I mean a Django app, you'll be writing your first Django app in a minute.
You can also download third party apps and plug them into your project from here on In when you hear app think module Django apps contain the code that outputs HTML They have a well defined naming structure and the files inside your app need to follow this.
Not all the files are necessary for all apps but most of them are pretty common for starters is the ap file.
This defines some metadata for your Django app and is a key part of how Django figures out what apps are in a project.
Django is roughly defined around the model, view controller presentation concept very roughly actually.
It kind of blurs the lines around the controller part.
This idea separates out the model of your data from the view used to present it The views file inside of an app contains yep, you guessed it.
The views used to present your content.
There are several different ways of defining views.
The simplest of which is a function that returns some HTML.
There are two common places for unit tests in a Django project.
Either at the project level or grouped in with the app.
I tend to favor the project level unless I'm writing a package herbal app that I'm shipping separately.
But if you want to associate your tests with your app, they go inside of test stop I inside of the app.
The model part of a model view controller represents the data in your software.
Django has a rich mechanism for mapping classes to content in a database.
This kind of feature is known as an O.
RM or object relational model.
The models file is where you locate the classes that define the data storage in your Django app.
A lot of web applications center around the Crud.
That's capital C capital R capital U capital D create read, update and delete operations of data.
The data is defined in classes in the model's file, but you often have to create a lot of pages for managing the creation and editing of the data that goes in the models.
Consider user management.
You need pages for creating users changing their info, resetting their passwords.
Deleting the list goes on to simplify this process.
Django comes with a powerful tool called the Django admin.
The admin is a series of pages for your models with a little extra code.
You get data management almost for free.
The admin file is where the admin code for your models in your Django app belong When you created your Django project, you saw the Earl's file in the inner alexandria directory.
This is the main place to map you RLS to code that outputs content.
You can also optionally define an Earl's file.
For your Django app, you have to add some code to the project level Earl's file to use an app level Earl's file, but is often a good idea to keep your app specific URL.
Mapping these together.
The final part of the app that I'll mention for now is the migrations directory.
Django comes with a mechanism for doing database migrations as you adapt your models over time, the corresponding tables in the database will need to change columns may get added or removed, tables may get added or removed, etcetera.
The migrations directory contains code that helps you manage these kinds of activities.
In a later chapter, I'll run you through all of the specifics.
|
|
show
|
1:17 |
Alright, I'm back inside my terminal.
Creating an app is similar to creating a project.
It uses a Django admin subcommand.
I'm still inside of the Alexandria project directory and this time I'm going to use the manage.py script.
The subcommand I used here was start app and primer is the name of the app.
Let me run tree again.
Ok lots of stuff has happened here.
First off notice that a bunch of .pyc files have shown up running the managed py command earlier has caused several of the files in the inner Alexandria directory to get imported.
So they've been compiled about halfway down here you'll see the db.sqlite3 file.
Django supports several different databases.
SQqlite is the default.
When I ran the development server to show you the rocketship picture.
This file got created.
The newly created Django app is named Primer and a directory with that name has been created.
This directory includes dummy versions of almost all the files I just told you about that are part of an app.
Creating the app is only the first step.
You have to register it with Django.
To do this you'll need to make a change to the settings file.
Let's go do that in PyCharm.
|
|
show
|
1:32 |
I'm now inside of PyCharm to edit some code files.
In the left hand explorer you'll see my Alexandria Django project.
Let me open up the inner Alexandria directory and this is where you'll find settings.py.
I need to make a change to this so I'll open it.
The settings.py file is a global registry that Django uses for all sorts of configuration.
As you might guess from the file extension, this file is run as code.
It's just Python.
Django looks for variables with certain names in here and uses that to build the configuration registry.
The first configuration change I'll show you is the installed apps list.
Let me just scroll down.
This list contains all of the Django apps that your project is aware of.
By default, this list is populated with apps that come with Django.
I won't go over the purpose of each of them.
But to give you an idea, the admin app is the CRUD data management tool I mentioned earlier and the off app manages authentication and logins.
The primer app I just created in the terminal using the start app subcommand needs to be registered.
To do that you have to add it to the installed apps list.
Most of the time, the order of this list isn't very important.
Some specific apps need to be picky about where they show up so it's good practice to just add your stuff to the end.
With the app registered, it's time to write some code.
|
|
show
|
1:55 |
I need to open up the views.py file now in the primer app.
This is the default content, I'm going to replace all of it.
The purpose of the views file is to contain code that outputs a response that is mapped to a URL.
For this first example, I'll keep it really simple and not even use HTML.
Just some plain text.
A view function in Django returns a response through an object called Httpresponse.
This class is found in the Django http module.
So I've imported it on line 3.
Now, let me define the view function.
A view function must take at least one argument called the request.
This argument contains information about the http request.
The name of the argument is only convention, but most people stick with it.
As I said at the beginning, I'm a traditionalist, so let's "Hello World!" The view function returns an Httpresponse object.
The first argument to the object is the content to be presented to the user.
In this case, that's the message containing Hello World.
The mime type by default is HTML.
As I'm going to be returning plain text.
I have passed in the optional content type argument setting it to indicate that the payload is just text.
That's it.
That's your first few.
Now, all you have to do is connect it to a URL.
|
|
show
|
2:31 |
Let's open up Alexandria URLs.py.
This is the default content of the Alexandria URLs file.
A comment at the top gives you some instructions on how to register a u r l.
There are several different ways of doing this.
The mechanism I'm going to use is to import the function.
I just defined in the primer.views file.
I then map a URL by calling the path function passing in the URL I'm registering and mapping that to the view that will handle it.
Path takes a string which is the URL.
That would be the say hello.
Notice the trailing slash, it's important.
Don't forget it.
And a reference to the view that will be called when that URL is hit.
In this case, also say hello.
You can name your URL s and your views whatever you like, but I tend to stick with this pattern.
My URL s and views are usually named the same thing.
It makes finding the code simpler, when you've got a complicated project.
I've got a view, I've mapped it to a URL.
I'm pretty much set.
I'm gonna go start up the development server and visit the view.
Here I have executed the run server management subcommand like before and like before it's listening on local host port 8000 and inside the browser, typed in the URL and there you go.
The world has been greeted.
Let's quickly review, you created the primer app to store your code by using the start app subcommand.
You registered the app by adding it to the installed apps list in the settings file.
You then edited the primer views.py file to create a view.
A view has to return some content.
I showed you how to use the http response object to return some plain text.
With the view in place, you map the URL.
The say hello URL to a view inside of us dot pi by editing the Alexandria URLs file.
With all that in place, when you run the development server, you can visit the URL that was registered and congratulations.
you're a web programmer.
|
|
show
|
1:28 |
Automated testing has become an important part of good development practice.
In the old days you threw your code over the fence to QA crossed your fingers and that's a bad idea.
Doesn't take long before you've got way too much code and not enough support from QA.
Modern development practices include a significant amount of time towards automated testing It isn't uncommon for there to be about as much test code as actual code To help support this, Django has supplemented the Python unit test framework.
You can define tests at both the project and at the app level.
Django provides you tools that allow you to mimic logging into a server, visiting a view and verifying the response content.
All of this is done without a browser.
That's both good and bad.
It's good because it means you can test a whole bunch from the command line and without any outside tools.
It's bad because it doesn't actually validate your page.
It just returns it.
If you're writing something that includes javascript, that javascript won't get exercised.
When building a commercial application, you often combine Django unit tests with tools like selenium, a record playback tool that operates inside of a browser.
The Django unit test can test the bedrock of your software including page flow and business logic, while Selenium or something like it can be used to test the presentation.
|
|
show
|
5:09 |
Different developers organized this next bit differently.
I mentioned you can put tests in your project or in your app.
I tend to keep all my tests together in the project unless I'm writing an app that will be shipped separately.
I'm sure if you dig around on the net you'll find someone who thinks I'm doing this wrong.
Wrong or not, I'm going to create a director here for my unit tests at the project level.
Now inside of that I'll create a file for the tests.
Like Python unit tests, Django expects these files to begin with the word test.
This is how the test runner finds them.
Your test case will live in a class that inherits from test case.
Good naming.
So the first thing I have to do is import it.
Test case is an overloaded version of Python's unit tests class.
I've defined a test class named primer test case.
Inside the test case I'll define a method starting with the word test.
This is the actual test that will be run.
Like with the name of the file, Django expects test cases to be methods starting with the word test.
This might seem redundant but it allows you to write helper methods in the class that aren't test cases simply by naming them something else.
The first thing I'm going to do in this test is to visit our URL Django's test case object comes with an attribute named client.
This is a mechanism that can be used to visit URLs.
Here, I've used the clients get method to visit our say hello URL, know that this looks like a web scraper but it isn't the URLs that will work in this mechanism are only those defined in Django maping's.
Django doesn't actually run a server when you run the tests.
When lines six runs whatever happens when you visit the URL the result gets stored in an object named response.
I'm going to run two checks on the response.
The first is the response code of the URL visited http has a series of codes that indicate success or failure of any particular URL visited.
You're probably familiar with the infamous 404 code which means there's no content for the URL you put in.
You may also have come across the 500 which is a server error.
That one typically means the software generating the web page crashed.
You might not know code 200.
That's the code for success.
You're getting 200's all the time.
But because the response was successful, your browser is rendering the result so you don't see the code directly.
The get method of the client returns a response object that has a parameter called status code.
That is where it stores the http response code, the test case class that the primer test case inherits from overloads Pythons unit tests and includes several methods for comparing values.
Here I've used the assert equal method.
If the response code is 200, then this code will continue to execute.
If the response was something other than 200, this code will exit and report an error.
The response object returned from the get also contains everything that came back from the view It stores this in an attribute called content because of the wonderful world of unicode, that content is actually stored as binary.
So before doing anything with it, I'm converting it to a string.
In this case I'm making sure that the content contains the word Hello.
You'll recall that the view said hello world.
This time instead of using assert equal, I've used assert in, if hello is found inside the content then the test passes.
You can also get at http headers and all sorts of information in the response object.
For now though, this test is good enough.
Let's go back to the terminal and run it.
I'm back in my terminal in the Alexandria directory.
To run the test, I just wrote I use the test subcommand of the managed pyscript.
Note, I'm on a Mac which is eunuchs underneath.
So I'm using the dot forward slash shortcut for running the script.
If you're on Windows, you'll have to type out the whole thing Python manage.py test The subcommand creates a special database just for the testing, checks that your configuration is sane, and then runs your tests.
In this case only a single test is found, it's executed rather quickly and passes.
Once all that is done the special test database is cleaned up.
And that's it, you've successfully run your first Django unit test.
|
|
show
|
0:53 |
Phew!
That was a lot in this chapter, you installed Django and used it to create a new project named Alexandria.
Projects are what probably should be called web applications, but of course that would be confusing because you also created a Django app called Primer Django apps are modules that you use to organize your web code.
Apps can also be supplied and installed from 3rd party sources.
Inside of your app, you wrote the say Hello View, a function that returned Hello World.
You then map that view to a URL, so that Django could serve the content.
Using the development server, you served that content and proudly visited a pretty but plain text web page.
Finally, because you're a responsible developer who wants to write good quality code, you wrote a test to go with your Django app and watched it pass with flying colors.
|
|
|
41:39 |
|
show
|
3:18 |
Built into Django is a template rendering engine that lets you bring concepts you take for granted in your code to your output.
The original writers of Django worked at a newspaper in the city of Lawrence Kansas and built the first version to help them create web applications.
They had both programmers and HTML writers on their staff and didn't want the HTML folks to have to learn Python.
As such, they built a mini language used for composing HTML based on templates.
The language is similar to the concepts in PHP.
You work directly in the file and then a series of special sequences allow you to control the contents.
Django template language has two main ideas, tags and filters, tags are actions and do things like looping and conditionals.
Filters modify data before it is output.
When you view a web page, you're seeing the text output resulting from hitting a URL.
That HTML document contains references to other things like images, CSS files and javascript.
Django refers to these other things as static files and provides tools for referencing them and serving them from the development server.
HTML tends to be rather repetitive.
It isn't uncommon for every file on a website to have the same header and footer The template language embodies dry principles that's don't repeat yourself and allows you to inherit and compose pieces of HTML.
You can manage your output, like you manage your code.
Django supports several different template engines.
It ships with both a Django specific one and support for jinja2.
You can also plug in other template engines if you prefer.
In this chapter, I'll be focusing on the original Django templating mini language.
The writers of Django made a design decision to intentionally not expose Python inside of the HTML.
This is to separate the roles of coder from HTML writer.
There are pros and cons to this decision.
The pros are the mini language is much simpler and is designed to fail silently, failing silently, makes debugging more difficult but it means mistakes aren't in the end users face.
The cons are you sometimes want to do something in the mini language that it wasn't made to do and you end up having to jump through hoops.
Of course, you always have the coding logic in your views to do fancier stuff.
To write in the mini language, you directly write the content and then use built in and third party tags to control the flow of the content, including loops, conditional and re use and then you can apply filters to modify the data in place.
For example, a filter can take a raw date and format it into a pleasing fashion separating again the data from its presentation.
The template engine is usually used to render HTML but can handle any text format.
I often use it myself to compose multipart MIME email messages and if all this isn't great enough, the templates themselves get compiled before being rendered.
This happens behind the scenes so you don't have to worry about it but it means the rendering step can be pretty quick.
|
|
show
|
2:34 |
To demonstrate the use of the template engine.
I'm going to create a new app.
This app will get used throughout the chapter.
I'm inside of my terminal within my Alexandria project.
Like in the previous chapter, I create an app by using the start app management command.
This new app is called home and will contain common pages like the home and about screens.
Here's the result.
Django created the expected placeholder files for the home app.
You tell Django where to find your templates through configuration in the settings.py file.
Best practices is for this directory to be named templates.
Like with tests, you can associate templates with the project and or an app.
Also like tests, I find it personally easier to keep them together in one place unless I'm writing a reusable app to be shipped separately.
Let me create a templates directory inside of the project and I'm ready to go.
Now I'll head off to settings.py and configure Django so that it knows where this is.
A couple of things need to be configured for the template ng engine Let me open up settings.py Remember that's inside of the inner alexandria directory.
On line 16, here you'll notice the base_dir value.
It uses Python's pathlib library to store the directory where the project lives.
This gets used throughout the settings file.
Remember it, I'll be using it in just a second.
Here I've added the newly created home app to the installed apps file and now I'll scroll down to the templates.
On line 55 is the templates definition.
The dirs value inside of the templates configuration tells Django where to look for templates.
I'm going to tell it to look in the templates directory I just created.
The path flip library allows you to join objects using the overloaded/operator.
Dirs now points to the fully qualified path of the templates directory.
That's the only configuration change you'll need to get going.
|
|
show
|
3:46 |
Now I'm going to go create a new template inside of the templates directory.
For a first stab, I'm going to keep the HTML pretty vanilla.
Opening tags and here's an H1 for a title.
The double braces here, sometimes called mustache operators, are your first bit of django templating.
Double braces mark a context variable to be replaced.
In this case the rendering engine expects a variable named heading, which it will insert into this spot.
If there is no heading variable, this will get rendered as an empty string.
I'll add some more HTML and since alexandria is a library, I'm going to include a list of books here.
and this is your first django tag.
The brace % combination indicates a tag.
The first word after that is the tag in question.
In this case it's a for loop.
The structure of a tag is similar to a for loop in Python, book in books here says to loop through the contents of the books container and get each item out one at a time.
The book variable takes on the iterated item for each pass of the loop, similar to the heading variable before the book's value is in the rendering context.
If there's no such variable, this loop gets skipped.
Everything inside of the for tag gets rendered once per loop iteration, Let me add some content.
Everything inside of the for tag gets rendered once per loop iteration.
In this case you'll get an li item for each book in books.
Each li will contain a title in italics because I've used the mustache operator.
Once again, this time I'm using book the value of the current iteration Like in Python you can get at attributes of a variable through the dot operator Here, I'm accessing the title attribute of the book value.
Unlike Python, the dot operator is used both for attributes and indexes.
You can use 0.3 to get at the fourth item in a container and dot name to get at a key in a dictionary.
The pipe operator inside of this same variable is a filter.
This modifies the contents to the left of the pipe before they are rendered.
The upper filter is similar to Python's upper method on a string.
It converts everything to uppercase.
So line 10 is the book's title in italics all upper case.
After that on line 11 is the author's name.
This time without a filter, line 12 is a conditional.
This is like an if statement in Python, if the value of book.recommended evaluates to true the content inside the tag is rendered.
When rendered, this page will show a header, a welcome message, and a bullet list of books.
Each bullet will have a title in uppercase italics the author and a suffix of exclamation marks, if the book is recommended.
That's your first template.
Let's write a view that uses it.
|
|
show
|
1:54 |
This is the stub file created by Django when I ran the start app command, let me just put in a comment header and now I'm ready to write my view.
You'll recall from the previous chapter that a view function returns an http response object A really common pattern is to load and render a template and return it wrapped in an http response object.
So Django provides a shortcut for doing all that called render.
This is so common that the stub provides that import for you out of the box.
Let me define a view.
I'm going to call it sample, and like all views, it takes a request object as its first argument.
Now I'll create a dictionary that is used as a context for template rendering.
Everything I put in this dictionary is treated as a variable inside of the template.
You're not exposing everything in your Python function to the rendering engine.
Just what is in the context.
The only thing I'm putting in the dictionary is a list of books.
With each item in the list, also being a dictionary representing the book.
If you think back to the template I just wrote, you'll probably be able to take a good guess as to what the output will be The last item in the view is the render shortcut.
Render returns an http response object.
And since views are supposed to return one of those, you usually just return the result of render.
Renders first argument is the request object for their view.
And the second argument is the name of a template file.
The last argument is the rendering context, in this case the data dictionary.
|
|
show
|
2:00 |
Remember that to display something, you have to map the URL to a view.
In the previous chapter, I showed you how to use the main mapping file in the inner Alexandria directory.
Here, I'm going to put some URLs together for just this app.
Let me create home URLs.py.
This file is structured similarly to the main one in Alexandria.
The key part here is the URL patterns value being a list of mappings for the app.
I've added a URL called sample and associated it with the sample view I just wrote.
This file on its own isn't enough Django doesn't know to look for it.
It is good practice to keep URLs in an app together, but you still have to register them all.
Let's do that now.
I'm going to open up the inner Alexandria URLs.py file and now I'll add all of the child URLs from the home map.
You do that by using the include function, note that isn't in the imports from the stub.
So I have to add it.
Now I'll register all the URLs in the home app under the home base URL.
The include function takes the name of an app's URLs file.
The result will be any sub URL defined in home.URLs will be included under /home than its own URL.
For example, the sample view I just mapped will end up at /home /sample with that saved, let's go check it out.
|
|
show
|
1:04 |
In the previous chapter, I ran the development server in a terminal.
You can also do that right here inside of Pycharm and now I'll hit /home /sample in the browser.
You'll recall that there were three books in the context and no header defined.
So the H1 tag here is empty.
The first book was Frankenstein, it didn't have a recommendation key in its dictionary which is treated as false, so no exclamation marks.
The 2nd book was Metamorphosis.
Who doesn't like a giant talking cockroach?
This did have a recommendation key so you get some exclamation marks.
The final book was Dracula.
It also had the recommendation key defined but it was set to false so only the cockroach book gets any love.
|
|
show
|
2:47 |
There are loads of tags and filters built into Django and you can also install third party tag libraries.
You can even write your own but that's beyond the scope of this course.
Let me cover a few of the more common tags.
You saw the four tag in the previous example for each item in a container, the contents of the tag gets rendered.
The value of item can be referenced as a variable inside of the context of the loop.
There are also some predefined values that get you info about the loop.
There is a counter this is like using enumerate in Python, a bullion to indicate if this was the first and another one for the last pass through the loop reverse counters and others as well.
The four tag also supports an empty clause that works like an else condition.
It only gets rendered if the loops container was empty.
This is particularly useful if you want to show a message like there are no books in your library instead of just an empty space.
This saves you wrapping your forloop with a giant if condition.
Speaking of you also saw the if tag it works like an if statement in Python Also like Python, it supports both an elif and an else clause.
The comment tag comments out a block unlike an HTML comment, this doesn't show up in the result.
It removes the contents from the result entirely.
It has a neat little feature that you can pass in a string that it also ignores.
This is handy if you're using the comment block to comment out a chunk of the page when you're debugging, you can write a note to yourself as to why you did it.
A comment within your comment.
I haven't covered it yet, but Django supports a mechanism for naming and referencing URLs This is good practice.
It means you only have to define the actual URL once and in all other places you use its name.
That way, if you want to change it, you don't have to hunt down all the places you used it in your code.
As often as you want to use links in your HTML, The URL tag allows you to look up a named URL It even supports constructing the parameters that get passed into the URL in both positional and keyword arguments.
You'll see more on this tag later in the course.
What if you want to show content that uses the same symbols as the rendering engine How do you escape them?
Well, there's a tag for that.
The verbatim tag renders the contents inside of it exactly as shown, ignoring any brace brackets that would normally be interpreted by the rendering engine.
You can even name the verbatim block so that you can have verbatim tags inside of it.
The name is in the opening and closing tags are matched and everything inside even verbatim tags gets rendered.
|
|
show
|
0:55 |
Filters modify how context variables are rendered.
This is the filter I used in the example before it converted the book's title into all uppercase.
There's also a lower tag that goes along with this.
The first filter returns the first item in a collection and you guessed it, there's a last that goes along with it pluralized, returns an S if what is being filtered is greater than one or a container with more than one item.
This is handy to construct sentences like you have three new messages you use pluralized instead of a giant if condition simply to make sure that messages does or doesn't end in an S as is appropriate.
It also supports an optional argument to specify how the pluralization is done.
So if you're paralyzing box, you can use the es suffix instead.
|
|
show
|
1:19 |
You've written a couple of views now and your last one output a template.
The output from those views, a resulting HTML document usually contains references to other content that isn't HTML, things like images, CSS and javascript.
Django refers to these as static content content.
That is just output.
You need to refer to it, use URLs that reference it.
But Django doesn't really need to do anything with it and it typically doesn't come out of a view In a production situation, you should have your static content served by a web server rather than by Django.
Django provides tools to simplify this process and manage your static files.
The development server will serve static files so you don't have to jump through hoops when programming, but like many things with the development server, you should remember, it should only be used for development.
In addition to static content, Django has a third category of output media files.
These are things the user has uploaded.
Like static files, the DEV server will serve media files, but you don't want Django dealing with these in production.
A later chapter, we'll talk more about uploads.
Our web pages would be pretty boring without some static content to go with them.
So let's go set up some static content.
|
|
show
|
1:49 |
You'll need somewhere to put your static files.
The usual place is a directory named static.
Let me create that and now I'll go into it and I'm going to copy a logo and fabric on file from the data directory in the code repo I now have a static directory and some static files to serve from it.
With those files in place, I now need to tell Django where to look for them.
You do that with some configuration.
Let me open up settings.py.
Now I'm going to scroll all the way down to the bottom.
The static URL configuration indicates the URL of where static files will be served from.
If you're in a production model, this will be the base of the URL that's something like Apache will serve those files from rather than Django.
In addition to this, I'm going to add the static files, dirs configuration setting.
This is a touple that specifies all the places to look for static files.
In this case only one.
Django uses this to serve the content from the DEV server and to manage the content if you're exporting it into a production environment.
Like the template dirs setting from earlier.
I've used base_dir as a reference point and reference that newly created static folder.
If your debug property in settings is true and you're using the DEV server.
The static files found in this directory will now get served.
You don't have to do anything else.
In production your debug property should be set to false and these should be being served by Apache or nginx or something equivalent.
|
|
show
|
2:46 |
When I created the directory I copied two files in a logo and a fab icon Pecans are one of the web's biggest hacks.
This is what happens when features get added to a browser without going through the standards process.
If you're not familiar, these are the little icons that show up in the browser's tab.
Your browser expects your favorite Con U R.
L.
Two B slash favor con dot I C.
O.
I just finished putting all the static content in the static directory associated with the URL Of the same name.
To get the fabric on to work.
You'll need a little extra code.
I'm gonna put that code inside of the inner alexandria.
Earl's file.
Django comes with some views out of the box, One of which is a redirect view when it is hit it sends a 302 telling the browser to look somewhere else for the content.
The redirect view is in Django views generic base.
Let me import it and now I can add a path specifically for the fabric keen The views I've shown you so far are function based.
Django also supports class based views.
The redirect view that I've just imported is actually a class based view with it being imported.
I can now use it and put in a U.
R.
L.
For the favor.
Con.
Right inside of this line.
I've directly in stan she hated the redirect view helper class.
The URL That I'm redirecting to is slash static slash Farrakhan dot I C.
O.
So to recap I registered A U.
R.
L.
Called slash favor con dot I C O.
When it's hit it gets a 302 to go to slash static slash fabrikant dot I C O.
And they're the static server can serve it like any other static file Pecans are actually very tricky.
You may need multiple files and multiple resolutions to make all the browsers happy.
The process for doing this is similar.
You just need to add more files and more redirects.
There are also third party libraries that manage some of this to make your life a little easier with that logo and fabric on in place.
I can now hit these, you RLS directly in the background.
I'll start up a DEV server and I can now go hit these images and there's the logo and there's the fabric on and in fact up here in the corner you can see the browser's already using it.
|
|
show
|
2:20 |
you've got some template rendering basics under your belt and you're all set to build a page that has static content.
So lex build an about page.
You may have noticed that there's a lot of repetition in HTML.
Every page you write frequently has the same nav bar headers, footers often even the same structure.
All that repetition means a lot of work.
If you decide to make a change, you wouldn't write your code like that.
So why write your HTML that way?
Django template engine embodies the dry principle.
That's don't repeat yourself.
It allows you to structure your output.
Use inheritance and compose reusable pieces.
The sample example that's fun to say from earlier was rather plain moving forward.
I'll be using the bootstrap library to do a bit of styling on the page.
Don't worry if you haven't used bootstrap before, I'll stick to the basics and point things out as I go along, Inheritance and composition is done in the rendering engine through the tag system.
There are four tags you need to know about extends is the inheritance tag.
It says that this file should use the file named in the argument as apparent to inherit from the block tag allows you to declare a reusable block of content.
This is most often named and used in conjunction with inherited files, allowing you to override blocks from the parent and insert new output within a block tag A context variable named block dot super gives you access to the parents version of a block.
This allows you to create a child block but put content before or after what was in the parents block by referencing.
The super block include is a composition tag.
It allows you to inject content from another file into the template at this location.
This tag can also take arguments so you can modify the context of the included file This can be useful in conjunction with a four loop.
You can loop around and include tag and pass in the four loops context variable into the sub template.
And the last tag you need for inheritance is the low tag It's used to load a tag library.
The tags you've seen so far have all been built in Jangle also provides some tags that are in modules or if you install 3rd party modules, you need to use this.
The low tag is the rendering engines equivalent of import
|
|
show
|
0:51 |
In case you haven't played with bootstrap before, I'm going to quickly highlight a few things, bootstrap provides both layout and widget tools.
The layout system is based on a grid, a grid is made up of rows and up to 12 columns.
The toolkit is responsive.
It uses CSS to manage the layout of the page, depending on the pages size.
This makes writing for different screen sizes like mobile, simpler.
When you write bootstrap tags, you can specify how this responsiveness adjusts based on different screen sizes.
Bootstrap comes with a good collection of widgets.
The two I'll be using for the about page are a nav bar and a wrapper for content that specifies screen width and friendly behavior for different screen sizes.
Alright, let's use all this together and write an about page.
|
|
show
|
4:51 |
the about page is going to inherit from a common base file To avoid that whole repeating yourself thing.
I'm going to call it based on HTML and it goes in the templates directory.
Let me create it.
The file starts with a load tag.
This is loading the static library.
I want every page to have online header that has the logo in it.
The static tag from the static library allows me to reference that logo without specifying exactly where it is.
You'll see all of that in a second here.
I'm just doing the import inside of the title tag.
I've declared a block.
This allows any child of this file to replace the title tags, contents in the page.
If the child doesn't override this block, it will get rendered as you see here says alexandria Django demo app, Bootstrap has its own CSS file needs all that text.
There was just to grab the bootstrap CSS file from an available C d N.
Speaking of CSS, I need a little bit of my own styling for the site I'm going to put that in a file called Sight Touch CSS under a new folder in the static area.
The static tag here returns the ural that belongs to this file.
You'll recall that in settings, the static you RL value is slash static.
This tag will create the full URL for the CSS file by joining that value and the argument here, this might seem overly complicated but it allows you to change where all the static files live without having to edit your HTML.
This can be particularly useful if you want to deploy your static files on a cd N and the URL needs to point to some other server entirely.
I've put a couple of empty blocks here to make it possible to inject some extra code in the head for links or javascript inside of child files that inherit from this file.
Okay, now to the pages actual content, the Nav tag is your first bootstrap tag.
This has a bunch of classes assigned that specify the styling for starters.
This is a simple nav.
It will only have the logo in the top left and it makes that logo clickable.
Taking you to the homepage which I haven't written yet, notice that the logo image is also using the static tag once again.
So that if this ends up getting served off a Cdn the URL isn't hard coded pages that inherit this page are going to want some content typically in the center that goes inside of a container block.
Bootstrap to find some styling that keeps your content in the middle of the page interacts with the nav properly and knows what to do with a footer.
The key part is the content block which is inside of the container block.
That's where all the inheritors will write their content.
So when I go to build the actual about page and want to say about this site, I'm going to be doing that in a block named content that will be inserted inside of the main bootstrap tags and all the styling will get taken care of and that's a little chunk of footer for the bottom of the page and here near the bottom, I've included J query some bootstrap widgets required J.
Query to work none so far.
But when the knob gets fancier I will need it.
So I'm putting this here now along with this is bootstraps own javascript file as well And in case the child page wants to add some javascript in the appropriate place I've added a block here.
That's a lot of stuff.
Huh?
The beauty is normally you'd have to do that on every HTML page, but with inheritance, you only have to do it once.
Let's create the about page and inherit from this base.
|
|
show
|
1:15 |
to inherit from bates dot HTML.
You use the extends tag.
This is like a class declaration with an inheritance marker inside of Python.
You'll recall that.
I put a block in the title tag.
I've overridden that here.
Now when this page gets rendered, the title tag will say what's in this block rather than the default in the parent And here I've replaced the empty content block in the parent with some actual content and the about page is done.
The pain of base dot HTML becomes the pleasure of about dot HTML.
All the pages moving forward can use this inheritance to keep the content simple and only including the stuff you're interested in when editing them furthermore, when it comes time to change the nav bar, you edit based on HTML and the results will show up in all the rendered Children
|
|
show
|
1:31 |
I've prepared a CSS file for you that does a bit of basic styling as I mentioned before.
That's inside of static in a new directory called CSS this file makes sure the footer is rendered at the bottom of the page by declaring a minimum height It sets some color information and does a bit of styling on the logo image with the underlying HTML.
Ready to go.
All you need now is a view.
I'm going to add 1 to the home app.
This view is going to be really short.
All that is needed this time is to render the template.
The context dictionary for render is optional.
So that's all there is to get an about view.
Now I'll add the about page two home dot girls remember this gets included by the include call in the alexandria Earl's file so it will be listed as slash home slash about.
With that done.
I'm ready to start my dev server and isn't that a thing of beauty?
It's starting to look like a web page.
When I defined the nav bar I made the logo clickable and set it to go to the base oral of slash.
Of course I haven't built that yet.
|
|
show
|
2:32 |
building a homepage will be similar to building the about page.
The only tricky part will be the U R.
L.
You have to register the slash with a view just like any other U R L.
Because that's in the base, I'll be defining it inside of alexandria URLs instead of the home dot Earl's place.
Even though that's the app, I'm going to put the view in, it's kind of the exception to the rule offscreen I created home dot HTML.
You'll notice except for the content blocks saying something different.
It's very similar to about dot HTML.
Not the fanciest thing, but good enough for now, back in views, there's the view for the homepage again, very similar to the about page.
And finally, just to register that slash remember that all the girls in home dot girls are going to be mapped under slash home so you can't put the route mapping of base slash inside this file.
This is a bit of a step away from the encapsulation that you might want for your app but like I said, exception to the rule, let me open up alexandria URLs.
And the first thing I have to do is import the views from the home app Technically I only need the home view, but this is usually how I import views as a habit.
So I'm going to do it the same way I normally do as all view files are named views dot pi I usually use the as clause of the import to make it clear where this is from.
So, instead of just importing home views, I import it as home underscore views now, I need to add the registration all you are L start with slash to get to say hello, you type the host name, then slash then say hello, but when it is registered, the leading slash isn't used Django does that for you?
Following that logic.
The base URL for the site is just the empty string.
So I mapped the empty string to the home function in the home apps views file and everything is set.
I've still got the DEV server running in the background so let me switch to the browser, Reload that previously problematic page and look at that your home.
|
|
show
|
1:45 |
unit tests don't usually exercise the rendered content too much and as this chapter has concentrated on the template engine, there isn't much new to see here.
As I mentioned in the previous chapter.
Tests can either be in the app or at the project level.
I prefer to keep them together.
So I'll be creating a new test file called Test home Inside.
I'll test that each of the pages I've created returned to 200 code and seeing as the sample example still fun to say had a context dictionary.
I'll show you that you can access it inside of the test.
Here is a test case for the home app like in the previous chapter.
This isn't a file that's name starts with test and is found in the tests directory and the test case sweet inherits from the test case class.
There are three groups of tests here.
All three use the client object to get a URL And then validate that the response code was 200.
The only new thing here is accessing the context.
The sample view had a context dictionary with three books in it.
A rendered templates, context is available in the response objects context attribute all I'm doing here is making sure there is a key called books inside of it but you could do some intensive tests if you like.
This allows you to do pretty exhaustive testing on a view without caring how its contents are rendered, leaving the testing of presentation to an outside tool like selenium.
I'll just open the terminal now and run the test to tests run successfully,
|
|
show
|
2:22 |
this chapter has been all about Django templates and how you can use this feature to apply the dry principle to your HTML templates.
Do actions through tags which are often similar to key words in Python.
You saw loops, condition als and inheritance to construct Web pages along with tags.
The template in language also has filters.
These help you change the presentation of data in the templates context.
This gives better separation between the data and its presentation.
For example, you could format a date inside of the template possibly based on a user's preferences instead of doing that in the view, views frequently used templates.
So, Django has a shortcut method called render that renders a template.
It takes a context dictionary for the rendering and returns an http response object.
HTML contains content of course, and some of that content references other content.
Django refers to these outside references as static files.
These include CSS javascript and image files.
Although the Django dev server will output these files to make your dev experience simpler.
Django shouldn't be responsible for them in production as such.
Django provides tools for managing these kinds of files.
I'll talk more about this in the chapter on deployment inside of your HTML templates.
You used the static tag which maps the URL.
S to the actual static files, which allows them to live in different places in dev staging and production.
A special case of a static file is the favor con which browsers expect to be in the root of the server.
Using the redirect view helper.
You can serve favor icons using the static system and map the route you are L to a static directory.
Instead, through the redirect 302, I showed you how to register a route you RL itself by mapping the empty string inside of the base you RLS file.
Finally, I sprinkled a little bit of bootstrap into your jangle course just so the pages could be a little prettier in the next chapter will take you from rendering content to storing data.
Oh.
RM is on deck.
|
|
|
39:35 |
|
show
|
5:14 |
You've seen how to get a site going and you've rendered some HTML.
That's all you'll need for a static style website.
But what if you need to manage data?
In this chapter, you'll be adding books to the database and then running queries to get that information back out.
Django comes with a database abstraction layer that lets you use objects to manipulate the rows in a database.
In this chapter, you'll be adding data to a SQL lite database through the use of model abstractions provided by Django if you've ever used the most excellent SQLAlchemy Library.
Django has a similar mechanism built in.
Models abstract away the database and changes to those models mean changes to the underlying data structures Django provides tools for managing these kinds of changes through a migration tool set.
And of course you're going to want to put data into your database.
So, Django also has some management commands for importing and exporting your underlying data.
These chunks of data are called fixtures.
What's computing without more acronyms?
Well, O R M.
That's object relational model even that's not quite helpful.
It means modeling or abstracting data through object oriented concepts.
Essentially, you get to use all that fancy class stuff.
You learn to map tables into a database.
The beauty of it is you don't have to think too much about the database.
The corresponding beast, is that more complex queries tend to be inefficient unless you truly understand how Django is mapping your object relations to the database.
I've seen some truly scary code out there where the developer didn't understand what they were asking the database to do but concentrated just on the class relations.
Nothing in this course we'll get that messy but be aware that it can be a problem at scale.
Out of the box, Django supports five different databases.
For this course, I'll be showing you SQL light.
This is a lightweight database that stores everything in a local file.
Doesn't require a server and ships with Django.
So you'll have nothing else to install.
If your favorite DB isn't here, you may be able to find a third party back end which you can use instead In fact, there are some out there for no SQL implementations if you prefer.
As your code evolves over time, the underlying database will have to reflect any changes in your models.
This typically is things like adding and removing columns.
Django comes with tools for managing this, which are mostly automatic, at least for the simpler cases.
Django also provides two management commands from importing and exporting data to and from your database.
It offers a couple of different serialization formats for your storage as well.
The resulting files are called fixtures and they're particularly helpful for setting up test databases For your test suites a model in Django is created by defining a Python class objects based on the class correspond to values in the database or are about to be in the database.
Attributes in the class declaration to find the column types in the database.
These attributes in Django parlance are called fields.
There are over 25 different types of fields and most of them work with all the supported databases.
When you define a field, you give it parameters which change how the field behaves.
This allows you to express default values, minimum and maximum values, the length of the field, and more.
The data fields correspond to the kinds of columns you typically see in a database which for the most part mapped to the kinds of data you see in Python, integers, characters, dates and more.
You can also define relationships between the objects.
In a short while you'll be creating an author object that is related to one or more book objects.
Django supports three different main types of relationships.
1-1, 1 to many like the author in the books, and many to many.
There are also more complex ways of creating generic relationships that are beyond the scope of this course.
Inside of each Django app, there is a models.py file where Django looks for the definitions of model objects Once you've written a model, you use a management command to map that into the database.
This is called the migration process.
The migration process has two steps.
The first creates a special Python script that defines the actual underlying sequel table, creation statements and the second runs all the scripts that are new since the last time a migration was done.
With your tables in place, you want to put some data in them or get data out of them.
Each class automatically gets a query manager that is responsible for this.
It is the interface to the underlying sequel, select update and delete statements.
You can use the query manager to formulate parameter arised queries.
These return an iterable query result object that contains an instance of your class for each row in the database that matched your query.
For example, you could ask for all the authors that have a last name that begins with T and get back a query result object with zero or more author objects inside.
Okay, enough chit chat.
Let's write some code.
|
|
show
|
3:14 |
Let me just open up an execution window here and I'm going to create a new Django app called catalog.
This is where I'm going to put all our author and book related stuff.
You'll notice that the new folder has shown up in PyCharm Now I'll add the newly created app to the installed apps listing by editing the settings.py file.
With that done, I'm going to create a model inside of catalog.
Now, I'll create my first model class one that contains information about an author.
Each Django model needs to inherit from the models.model class.
I'm trying to think of a way of saying Model one more time in that sentence The base class defines the query manager.
Some default methods like saving to the database and is also a declaration to Django that you want this to be a Model.
Models have fields that contain data, each of which maps to a column in the database.
Let's start by adding a field to track the author's last name.
This line tells Django that you want a character field column in the database named last_name.
Sequel has two ways of declaring string like things.
The first is a CharField which must have a length.
The second is a less efficient field called text which doesn't have a length.
Unless you're storing arbitrary text blobs, you should prefer the CharField.
The max length argument to the field specifies how many characters the database should reserve for storing this data.
Alexa had a first name.
This is pretty similar.
The only new thing here is the blank equals true option There are two different kinds of empty.
For a text field in a database you can have an empty string or you can have null.
Both the last name and first name fields are using an empty string.
Unless you have a very specific need otherwise, this is how you should specify your empty text in a database.
So then what's this blank thing?
The blank attribute tells Django that when validating this field it is allowed to have no content.
Note that this is a Django level thing rather than a database level thing.
In the future, when you build a web form that uses a data model underneath or you use the Django admin to fill in a similar kind of form.
Django uses this blank indicator to allow the first name field to be empty.
As the blank parameter defaults to false the last name field will be listed as required in any kind of corresponding form.
If you try to save an author object that violates these conditions, you'll get an exception.
Okay, how about some numeric data?
There are a whole bunch of different kinds of number fields and quite honestly, this is probably not the best choice for storing a year, but it's good enough for now.
|
|
show
|
2:25 |
Alright, you can store an author let's add a book class.
Unlike the CharField, the text field is unlimited.
It tends to be less performant in the database.
I could argue that this should also be a CharField but then you'd not be seeing a text field in use.
This is your first relationship field.
By default, every model has an identifer named ID And Django sets this automatically as the primary key for the table.
A primary key is an identify their unique to a table.
That can be used to specify relationships between objects.
A foreign key is just the primary key of an object in a different table.
In this case, the author field on the book model will contain the primary key of a row in the author table.
Thus denoting the relationship between this book and that author.
The foreign key field has a couple of required parameters, the first of which is something that specifies what object this book is related to.
That's the author class.
This can be specified either as a class or as a string containing the name of a class.
The string can help you get around having to define your classes in a certain order The on delete argument instructs the database on what to do with this object.
If the foreign key is deleted, setting this option to cascade indicates that if the author is deleted, you should also automatically delete the corresponding books.
Other options let you leave the book intact but to wipe out the value of the author key, it's up to you which you want to do.
The options to the foreign key also have another use of that blank thing I was talking about before.
In this case, specifying that informs this field can be empty unlike a string which has the option of being an empty string, the only way to leave a key empty is with the null indicator.
That's why you have both the blank and null arguments to this class The null is enforced by the database and the blank is enforced by Django.
This can be a bit confusing at first, but you want to be careful to set them correctly.
Otherwise you might end up with the database rejecting the field having no contents even though the Django admin says it can be blank.
|
|
show
|
0:38 |
Let's add two more fields to our book.
These two fields both stored date timestamps.
The auto now add option says to automatically set the value of this field to the date time when the object was created.
The auto now option says to automatically set the value of this field to the date time of the last time the object was changed.
When you first create a book, these two fields will have the same value.
If you edit the book, the created field will stay the same, but the updated field will have the new timestamp of the edit.
|
|
show
|
1:20 |
Alright, you've got two new models, author and book.
Now, it's time to ask Django to put them in the database.
Let me just go to the execution window here.
The command you need to run first is make migrations.
This will examine all the model files and see if anything has changed since the last time you ran that command.
As you've added two new models, it will pick that up.
I'll get into the details of this a bit later But for now just understand this is how Django creates scripts that make the database changes for you.
With the migration files made, the next step is to actually run the underlying scripts.
When I ran that command, Django looked into the database where it tracks the state of all objects and checked the current version of all the migration scripts.
It would have found no version number for the catalog app, seen the new catalog migration script and then run it.
The migration table in the database is then updated with the catalog migration scripts, version number.
This way, Django can compare the state of all your model files against the state of the actual database.
With the migration complete, I'm going to open up a bash terminal and show you your brand new models in action.
|
|
show
|
2:28 |
Django has its own version of the Python Rebel.
Using this instead of the rebel means that a bunch of environmental stuff Django needs is already taken care of this means you can skip a bunch of steps you'd otherwise have to do to tell Django where the project lives.
You get into the Django Rebel by running the shell management command.
If you're on Windows remember you'll have to be less lazy than I am and run the full Python managed shell instead of using the eunuchs dot slash shortcut.
Total aside, but if you're going to be doing a lot of Python and Django in the Windows world, I'd encourage you to learn how to use the Windows subsystem for Linux.
It allows you to run a Linux instance inside of your windows, giving you the best of both worlds, 99% of the time you're production.
Django code is going to live on a UNIX based OS.
So you're more likely to solve hosting problems before they happen if you're coding in a similar environment.
Because it runs under Windows, you can get at all the same files and use all the same tools you're used to, like I said, the best of both worlds.
Anyhow, here I am inside of the Django Shell in order to play with our models, the first step is to import them.
Now, I'll create an author, there's a bunch going on here.
Remember when I said that every model inherits from models.model class.
One of the reasons for that is to give you access to the query manager.
The query manager is available as an attribute on your model named objects.
This query manager has a bunch of methods which allow you to interface with the database The create method creates an object and the corresponding row in the database.
The call to create requires the named arguments for any of the required fields defined in the object.
In this case, I'm creating an author object, sending in the last name and year of birth.
Looking at the Dickens object, you get a typically terse bit of rebel printing telling you that Dickens is an author object and the one in brackets is the primary key or object I.
D.
You can get at the fields in the object through dot access.
The last name field contains Dickens.
That's good it should.
|
|
show
|
0:56 |
The query manager is appropriately named.
You use it to query the database.
The all method returns all the objects for this model in the database.
In this case the single Dickens object with an I.D.
of one that I had just created.
X axis, the first name field.
It's empty.
That's because one it isn't required and two.
I didn't specify it, when I made the call to create.
The Dickens object can be thought of as a proxy to a row in the database, changes to the object won't change the database immediately.
You have to remember to call save afterwards or the changes will be lost.
Let me assign a first name to Chuckie and then I'll save it to the database.
The database now matches the state of my object.
|
|
show
|
0:44 |
Another method of the query manager is get.
This returns a single object that maps to the arguments given.
In this case, I have asked for an author with an id of one.
This gets returned into the result variable.
Note that get will return an error if there are multiple objects that match your query In the case of the id, which is the primary key, this won't happen.
There's the result and results last name and id.
And just as a reminder, here's the Dickens object and you can compare the two.
|
|
show
|
1:55 |
Great.
Let's create some books.
Three books created with the ids 1, 2 and 3.
Now I'll query the books, once again, I've used the all query method.
There's a lot there a little hard to read but you can see ids 1, 2, and 3 show up in the query set.
So far, you've seen the query managers all and get methods.
Now let's add to your repertoire.
The filter method is kind of like get but it returns multiple matches.
The title argument to filter here says to find one or more books whose title is Hard Times, there's only one, but note that a query set comes back, Get returns a single object, filter always returns query set.
Even if that query set has 0, 1, or more objects in it.
Let's try another query.
The filter method can take objects as arguments.
Here I've asked for all the books that have a foreign key equal to the Dickens author object.
Any object that has a foreign key pointing to it automatically gets another query manager created.
The query manager will be named the same thing as the object with the foreign key plus underscore set.
This is a convenient way of finding related objects directly from the first model.
Let's look at an example, remember that the book model has a foreign key pointing to author.
So author gets a second query manager named book_set.
Like the objects query manager, I can call the all method on the book_set query manager.
This is the equivalent of the filter on the book model querying the author set to Dickens.
|
|
show
|
1:04 |
The debug info in the rebel wasn't too helpful.
Fortunately, there is a way of defining it for yourself.
Let me just add a couple of lines here to the catalog models.py file.
If you haven't seen double underscore str before which is pronounced dunder string by the way.
This is a special method in Python that gets called when you convert an object to a string.
Django uses this when you query things as well as in the Django admin.
If you don't want the less helpful book object bracket one, you'll want to overload this method like I've done here.
The F string online 18 contains the fact that this is a book object with the given id, title, and authors id.
Note that you can get at the author models fields through the book's author atribute.
I'm doing that here showing the author's primary key inside of Dunder string.
With that change in place, let me go back into the shell.
|
|
show
|
0:54 |
Importing and running a filter query and the resulting query set now has more detail in it.
Same goes with a get but of course, because I didn't add a Dunder string method to author, you will still get the default when you access the author attribute of the book.
That author attribute is as good as an instance of the author object.
I can get at its fields directly here.
Seeing as I'm here, let's try one more query.
Want to guess what this one did.
Django uses the double underscore as a way of triggering comparison operators.
This filter query says, give me all the books whose ids are greater than that's the GT 1.
That'd be books two and 3, of course.
|
|
show
|
2:12 |
I'm back in my bash terminal, this time, I'm going to show you the effects of all that object creation.
I have a copy of sqlite admin installed.
Let me just open up the database that Django created.
The tables command in sqlite lists all the tables in the database.
There's much more here than you might have expected.
That's because Django sets up a bunch of user management stuff and configuration stuff in the database for you.
All the tables starting with off underscore are user management.
The two tables starting with catalog are the two models, the model maps to a table named appname_modelname.
There are ways of changing this but there's seldom reason to bother.
The tables that begin with Django, our system storage.
The Django migrations table is the one that tracks the current state of all your model migrations.
Let me select everything in the catalog book table and you get a row for each book that was created.
The pipe symbol is used to separate the table columns in the print out.
The first column is the primary key, the objects automatically created id.
The second column is the text field containing the title.
The 3rd and 4th columns are the created and updated timestamps and the last column is the foreign key for the author table.
I only ever created one author whose id was one and so the value is the same for all three of these books.
Let me select everything from the authors now and you get back good old Chuck.
control deed, exit sqlite And I'm back in my shell.
Django also provides a management command that does essentially the same thing.
Why you might ask, so you don't have to remember the name of your database command line tool.
You can just use the Django one.
Running the dbshell command gets me to the same places before and just to prove that I've run tables again.
|
|
show
|
0:54 |
Great.
So you've got some models.
You've got the corresponding tables.
Now you probably want to play with more data.
Rebel is great and all but typing books in individually could take a while depending on the size of your library.
Good thing that Django provides some more management commands to assist you with this.
The chunks of data you import and export are called fixtures.
You have several different options for the format of these fixtures, including two variants on JSON, XML YAML.
As long as you install the PyYAML library as well.
The serialization is pretty much exactly as you would find things in your database.
That means that the primary keys in the data end up being hard coded.
This can be a bit painful if you're doing edits by hand in your serialized data As object relationships are all specified with those primary keys.
Let's go back into the shell and play with some fixtures.
|
|
show
|
2:12 |
I'm back in my bash shell and I want to get some data out of my database.
I do that by running the dump data management command.
This command requires at least one argument.
The name of the app whose data is being dumped.
That's a little squishy and hard to read but it's a list of dictionaries in JSON format.
Each dictionary specifies the model being serialized the PK or primary key and a sub dictionary containing the model's fields.
Using a little command line trickery.
I can redirect the result of that data dump into a file named catalog.JSON Now I'm going to delete the database if you're a Windows person meet one of the most dangerous commands in UNIX.
rm, short for remove it deletes files.
It deletes directories.
If you've got the right permissions it will delete your entire operating system.
Go ask a UNIX graveyard.
I've never met one who didn't have a how I messed up by using rm story With the database gone, I'm now starting from scratch.
Let me perform the migration.
Remember that the data on what was migrated was also stored in the database, so destroying that database, destroys that information.
Running migrate, creates a new database with all those tables you saw before in sqlite.
Let me go back into the Django shell, import, run a query and hopefully that's not a surprise.
Back out to the shell and now I can take the serialized data created by the dump data command and load it into the new database using the load data command.
The command takes the name of one or more files containing data as arguments and then tells you how many objects were loaded from how many fixture files.
Back into Django is ripple importing, querying, and isn't that beautiful?
Everything is back the way it was?
|
|
show
|
1:13 |
You can also associate fixture files with an app.
By doing so, you can load data based on the app name rather than on a file name.
This can be helpful if you're creating a shipable third party app that requires some data associated with the models.
Let me just set this up so that I can demonstrate.
First off, I need to create a fixture directory.
Then I'll take the catalog JSON file created by dump data and move it inside the fixtures directory.
Now back to the terminal to wipe everything out and show you the magic.
Back in Ashland, wipe the database once again, migrated, and ran the load data.
Similar to before, but this time I'm specifying the catalog app name instead of the file name.
This can also be a nice shortcut.
If your app happens to have multiple fixture files, they can all be loaded at once.
If they're in the right place.
|
|
show
|
2:14 |
Let's take all this database stuff and add it to a view.
Offscreen, I've created catalog views.py.
You may remember creating the sample view a while back.
It had a bunch of dictionaries representing books.
This view is almost the same.
The difference is now instead of there being a bunch of hard coded dictionaries, I query all the books in the database.
on line 9.
Query I'm using here is similar to the all query, but instead of just returning all the books, however, the database wants, I'm specifying their order using the order by method.
This query says order by author's last name.
Then by the book's title's, the double underscore here allows you to specify using the authors attribute of last name following the relationship between book and author.
So this query says get all of the books ordering by the author's last name and then by title.
Like the sample view.
All this is sent to the render shortcut in this case using a new template called book_listing.html.
Let me open that up.
Also off screen, I created templates book_listing.html.
I've been busy offscreen.
This is an echo of sample.html, this time I'm extending the base then, on line seven I have a for loop tag.
The loop is iterating through all the books in the data context, spitting out the title in bold and some author info Of course I need a catalog URLs file Where I define the book listing path and then I have to include this file in the master alexandria URLs file.
All things you've seen before, line 18 includes all of the URLs in catalog.URLs under the catalog path.
Let me fire up the server and now I'll visit the URL.
Ain't that grand?
There are all our books.
|
|
show
|
1:36 |
Inside of the settings.py file you'll find a dictionary called databases which is required This is what it looks like when you create a new project by default.
Django actually supports using multiple databases at the same time.
It isn't a common use case but boy is it handy when you need it.
The default value here is the definition of the database that is used.
If the code doesn't actually explicitly ask for another database, the engine value specifies what back end code to use to interact with the database.
Out of the box, this is set to sqlite3.
The other configuration here depends on what your back end is.
In the case of sqlite, all you need to do is specify the location of the database file here.
It's the project directory plus the name db.sqlite3.
As I mentioned before, Django supports multiple database types and the configuration is different depending on what you're using.
For sqlite, you specify the file name.
For Postgres under Django 4, you specify a Postgres service file and password file.
The service file contains things like the host name and the ports of the server.
For most of the others or Postgres, prior to Django 4 you specify the host name, port, user name and passport as part of the configuration.
If your favorite database isn't part of the Django package, there are lots of third party back ends out there that you can install.
Even some no ....
if that floats your boat.
|
|
show
|
0:51 |
Great.
So how do you go about testing all this stuff?
Well, Django has got your back here too.
When you run the test command, Django creates a special database for you.
Just for the testing instance.
Each test that gets run starts with a database transaction which then gets rolled back after the test completes.
That way, each test starts with the same state.
You wouldn't want the order of your tests to influence whether they pass or fail.
The test database starts out empty.
But there are methods you can define that get run when it is set up.
This is typically used to create some data common to your tests.
For example, a user to access your site.
There are also tear down methods that get called afterwards in case you need to do cleanup on some files or something.
These don't get used as often.
With this information in hand, let's go write some tests.
|
|
show
|
1:01 |
I've pre created a test file here with the basic test definition.
First off, I'm going to write a little helper method.
This is just a quick debug method that prints some info to the screen.
It will help you understand the state of things at each step in the test script Remember that Django uses a clean database each time the test suite is run, unless you do something about it, it will be an empty database.
This setup method gets called before the execution of each test method.
My setup has a print statement to tell you that setup is running.
Again, so you can see this test in action.
Then it creates an author named McTest of the clan McTest, of course, and a book on testing how appropriate.
|
|
show
|
0:37 |
Stringify, my first test checks the stringification of a book, the book will get converted into a string, meaning the Dunder string method will be called, and the test will check that it is formatted correctly.
This test calls the print state helper method on entry and exit.
Again so that you can see what is going on.
|
|
show
|
1:07 |
My second test, tests the book listing view.
Like in the tests for the home app.
I'm using the built in client to do an http get.
On line 35, I checked that the result was a good status code and after that I accessed the responses context dictionary.
This is a handy way of testing a view without looking for snippets of text inside the rendered HTML.
This contains the same context that the calling view passed to the render shortcut.
As the test suites set up only creates one book.
There should only be one book in the resulting context dictionary.
Like test stringify, this test is bookended, see what I did there by calls to the print state helper method.
Alright, I've got this in place.
Let me go run it.
|
|
show
|
1:39 |
To show you the difference between the test database and the project database.
Let me start by showing you the project database.
Yet another handy method for the query manager.
This one counts all the objects.
There are still three books, back out to the shell, and now I'll run the stringify test.
Okay, lots going on here.
First off you can explicitly call a single test by naming it.
Here, that's the tests module.
The tests, catalog module inside of it, the catalog test case class inside of that file.
And this test stringify method of the catalog test case class, it's a lot of typing but it gets you to a single test.
The first three lines come from the test suite.
Then there's a long line of equal signs.
Underneath that, you'll see the debug from the setup method at the beginning of setup there are no books and no authors.
By the end of setup there are one of each.
Then test stringify gets called and it says there's one book and one author.
Those are the ones created by setup and because test stringify didn't create anything new, one book and one author on the way out.
Our real database has one author and three books.
Our test database starts out empty and then set up creates McTest and her book.
|
|
show
|
0:49 |
Now I'll run all of the tests in catalog test case.
And let's look at the difference, below the first set of equal signs.
You'll see setup got called and then test listing.
Then below the second set of equal signs.
You'll see setup got called again.
Note that it starts out with no books and no authors.
Django has rolled the database back to the initial state before running the 2nd test.
This is a good thing.
It means that if one of the tests changes the database, it won't impact the other tests.
You don't have to worry about the order.
The tests are run in, both test listing and test stringify got run successfully and then Django helpfully destroys the test database for you.
|
|
show
|
0:45 |
There's lots more to the ORM I've only begun to scratch the surface.
You can modify your models to specify the default query order.
Cause validations at the field level, cause validations at the database level, and do even more funky things with your queries such as grouping results aggregation, think summing up some values, and returning dictionaries or individual fields instead of models.
There is a complete obstruction of querying the database as well.
You can build special queries by creating query objects or if you need to fine tune things, you can specify the SQL directly although you really want to know what you're doing before you try that.
|
|
show
|
1:33 |
Tired of databases yet?
Let's review everything covered in the chapter.
Django has an ORM built in.
It uses Python classes to define database table structures and instances of those objects as rows in the database.
To create a model you inherit from models.model class, and use fields from the same module to define the columns in the table There are a couple of dozen fields that map to SQL data tapes and for the most part are represented in the object by similar Python data types.
There are a special set of columns that specify relationships between objects which map to foreign keys in the database.
Django provides a set of commands and automatically generated scripts for managing the changes to the database that correspond to changes you make in your objects.
This is called migration.
There'll be more on this in a future chapter.
Once your table structure exists, you can get data out and put data in through the use of fixture files.
These are serialized text representations of your data.
And finally, Django automatically creates you a clean database so that you know the state of the system you're testing.
Special methods on the test class, provide for the creation of data on each execution of a test method.
Django comes packaged with a powerful tool that gives you a way of managing all your data through a web based interface.
It's called the Django admin, and that's what we will be covering in the next chapter.
|
|
|
34:08 |
|
show
|
2:16 |
The Django admin is a package that ships with Django and gives you a web based interface to your models.
If you're okay with the interface being a little bit clunky you get a lot of code for free.
Stuff you would normally have to write yourself to manage your site and its data.
This chapter concentrates on the Django admin.
But to do that first I need to take a little detour and talk about users The admin is behind a login wall so you'll need to know how to create a user name and password to get through that wall.
The Django admin is heavily customizable but by default is really intended for the site admins.
Hence its name.
So that detour I mentioned, it is really going to focus just on super user accounts.
The bulk of this chapter will be talking about the Django admin itself and how you can modify your models to make it easier to navigate.
The screen in the admin you're likely to be using the most is called the change list.
This is a listing of all the objects of a certain model, i.e.
the rows in the database.
If you've got a lot of stuff in your database, this can be information overload.
So you can create filters to help you look at just a subset.
Finally, the admin is just another web page.
So with a little bit of knowledge about how it works.
You can parameterize links to get the view you want, you've heard me mention before, how important it is to get your CRUD right.
Still fun to say, CRUD is an acronym for all the operations you do on data, create, read, update and delete.
Think about a user in your system.
You need a way to create new ones, allow them to read their profile, update their profile, and if you're a social media site a way to lie to them about how you're deleting their data when they requested, honest, honest you are because these operations need to be done on almost all data It would be convenient if someone wrote some code that did this stuff.
Django Admin goes a long way to covering your most frequent CRUD needs.
And because of the way models are defined, it takes very little extra Python to take advantage of it.
As the models are a proxy to the database.
The Django admin really is a web based interface to your database or at least those parts of the database that you wish to expose.
|
|
show
|
2:13 |
I mentioned in the intro that I'd have to take a little detour to talk about users, here is that detour.
Django has three basic classes of users.
The admin or super user.
The terminology actually is a bit inconsistent.
Staff, these are users who aren't quite super users but have access to the Django admin.
This gives you two levels of control out of the box and then normal typical everyday users, not that you should judge them.
The difference between these user types is indicated by the presence of two billions on the user profile.
A flag for admin and a flag for staff.
Best practice is to make all admin staff and both billions being false means a regular user.
There is a login wall in front of the Django admin and by default only those users who have the staff or admin flag turned on are authorized to use the tool.
Django actually has a whole other user group permission structure that gives fine grain control if you so desire.
The staff/normal user division is often good enough for most purposes though.
To use the admin, you're going to need a super user account.
Let's go use a management command to create one.
I'm in my terminal shell the management command I want is create super user.
It prompts me for a user name.
The typical one is to use admin, then an email address.
It doesn't verify this.
You can use anything that is a valid looking address.
Then a password.
Then to make sure you can type the same thing twice in a row It asks you again.
And then in my case it yells at me because I used a totally insecure password.
Inside of settings.py, you can control exactly what password validators you use.
Or you can do like I am here and tell the create super user command, it's all good, admin is a perfectly acceptable password for the admin account.
Please don't do this in production.
And that's it.
You now have a super user account.
|
|
show
|
1:23 |
In the background, I'm running the development server.
The URL for the admin is /admin, easy enough to remember.
Django has locked the admin away behind an authentication wall.
So now I'll use the super user account I just created to sign in.
The password is super secret and hard to guess.
And this is it, not much to look at, is it?
That's because very little has been registered with it yet.
By default, all you get are the users and groups models which are part of the authentication framework.
By clicking on a model name, you can go to the change list screen.
The change list screen, lists all the objects for the given model type.
In this case the users, as the only user in the system is the super user I just created, this list is pretty short.
The change list screen shows some of the attributes of each of the objects listed.
For the user, that's the user name, the email address, first name, last name and staff status.
That pretty green check mark tells you the admin user is also a staff member.
This screen is the read part of CRUD.
Clicking on the user name will take you to the update part of CRUD.]
|
|
show
|
2:09 |
There's a lot on this screen starting up at the top with the user's name.
This is editable and there is a little hint about what characters are allowed.
The password isn't editable.
Django uses a one way hash to store passwords so there is no way of viewing the actual password.
There is a little link here that allows you to change the password though.
See the admin is being helpful already, your help desk people could use it to do password resets for forgetful users.
Next are some of the users attributes.
Let's fill in a first and last name.
That's Armin McAdmin of the highland admins.
You have to scroll all the way to the bottom to save.
I'll take you there in a second.
The permission section has three key flags.
Active staff and super user.
Removing the active flag stops the user from being able to log in.
The staff and super user flags are what I described before.
There's even a little text to indicate the difference, staff have access to log into this admin area and super users have all the permissions regardless of whether you've assigned them specifically or not.
I mentioned that Django has a robust permission management mechanism.
This section is where you say who belongs to what groups and what their permissions are Quite frankly, I've almost never used this.
I find I either am good with the simple staff, non staff distinction or I need really hardcore permissions that send me towards third party libraries.
But lots of people find it helpful and it's here if you want it.
Further down, you have some info on when the account signed in and when it was created.
The three kinds of save buttons help you create or update.
And of course the delete button is for deletion.
All the CRUD without the R.
I'll just save the changes I made to the account's name by clicking on save.
I'm now back at the change list screen and Armin McAdmin is a thing.
|
|
show
|
3:02 |
All right, you're all set.
You've got a super user account.
You've seen the Django admin, you've updated a model object.
Now, what?
The next step is to get your own models into the Django admin.
This is done in a similar fashion to how models are declared.
Instead of using the apps model.py file, you use the apps admin.py file instead of inheriting from models.model, you inherit the model admin instead.
You then register this new class against the model that it represents and that's all you need, but you can do more.
You can customize the model admin class to change what is shown in the admin and what can be done to the model.
Let's go into pycharm and I'll show you how it's done.
When I ran the command that created the catalog app.
Django created a sub admin.py file for me.
Let me open it up and now I'm just going to make a couple additions.
This is the module you'll need to do just about everything.
Like I did with the models module rather than import the classes underneath.
I import the module itself and use it directly.
Not sure why I do it this way, But it seems to be how most Django examples do it.
So I must have picked up the habit along the way, as I'm going to be admitting the book and author models.
I'm going to need them.
So here I have imported them.
These three lines are the minimum you need to have your model show up in the Django admin.
The register decorator takes a parameter which is the model.
This admin model is going to administer.
In this case it's the author object.
The author admin class inherits from the admin.model admin.
You thought me trying to say model a lot in the same sentence was confusing.
Of course you can name this whatever you'd like but best practice is to name it the same as the model being administered plus the word admin.
Like I've done here, declaring the class and wrapping it with the registration decorator, means that Django can now find this object and we'll use it in the web interface.
Let's go to the web and take a look at what's there.
With the code in place.
The author object now shows up in a section called catalog named after the app.
Notice that it paralyzes your model name automatically.
This can be a problem.
I'll talk about it later.
If I click on the author's link, I get the authors change list.
Reminiscent of the rebel, this isn't a particularly pretty listing.
In fact, this is exactly what you would see in the rebel if you printed out one of these objects.
The number in brackets here is the objects id, Thankfully there are ways of making this more useful.
Let's go back to pycharm.
|
|
show
|
1:11 |
I'll leave the author alone for a moment.
Instead, let me write an admin model class for our books.
The list, display attributes of the admin class indicates what fields get shown in the change list screen.
By default, you get one thing the objects rebel representation by setting list display to a tubal containing the id and title strings.
You'll now get a change list with two columns.
Let's go take a look.
I've refreshed the homepage and the book has shown up in the catalog section.
Let me click through.
This is the books change list.
That's much more readable than the author case.
The titles of the columns are also clickable to activate sorting.
I'll just click the title column and now the books are sorted.
Once a column has been sorted, a little arrow shows up, allowing you to sort in the other direction.
There's also an indicator for turning the sorting off, clicking takes you back to whatever the model's default sorting is.
|
|
show
|
1:45 |
The list display attribute can indicate any field in the model or a colorable in the admin model.
Whatever that cullable returns is what is shown for the row.
This is how you can get at things like the author's data inside of the books row.
I'll just add a new item to the couple called author last name.
And this is the format of the callable that the admin model expects.
It takes an argument which is the object that is being printed.
This method will be called once for each row in the change list screen, with the object that corresponds to the row as a parameter.
Any string return from this method will be shown on the change list screen.
I'm returning the book's author's last name.
Now back to the browser to see this in action.
Refreshing the page and there it is.
Each book now shows the author's last name.
Notice that you cannot sort on this column.
The sorting isn't done in the web interface.
It is a call through to the back end which changes the result of the underlying SQL query.
As you can return absolutely anything in the callable column method Django has no way of knowing how to change the database query.
The admin uses pagination when you have a lot of records.
This pagination is directly linked to the SQL query.
If you have a lot of records sorting them by that callable column would mean loading all of them into memory.
Django doesn't want you to do that.
So it sticks with using the database query for this kind of stuff.
|
|
show
|
1:17 |
There are two links for adding books, one in the top corner and the other in the section area, Clicking on either takes me to a form where I can add a book.
The area for the title field here is quite large.
That's because if you'll recall, the title is a text field rather than a charfield.
The default rendering in the admin of a text field is to use a text area rather than an input block.
Because the author is a foreign key, the Django admin provides a dropdown listing of all the existing authors.
Still not particularly readable, I'll come back to that later.
In the meantime I'll pick one and now that I've saved it shows up in the change listing.
There are two ways of deleting it.
You'll remember there is a delete button on the update screen or you can click the little checkbox to select your record and then choose delete selected books from the actions.
Pushing go, one warning screen later and there it is gone.
|
|
show
|
1:09 |
If you have a lot of records, the Django admin will paginated the view for you.
Of course if you have a lot of records you might end up clicking around a lot before figuring out what page the thing you're looking for is on.
The admin helps with this problem by providing a method to filter your pages.
The list filter attribute tells the Django admin what fields your object can be filtered by.
In the case of related objects.
What shows up in the filter listing is that nasty old rebel representation that you learn to override in a previous chapter.
Let's go do the same thing for author and then add a filter.
I've added a filter to the book object before doing anything else.
Let's go take a look at this.
I'll refresh and a filter panel shows up on the right hand side.
Allowing me to click and see just those things that belong to a given author.
Of course, unless you've memorized the authors id umbers, this isn't quite helpful yet.
Let's go do something about that.
|
|
show
|
0:37 |
Just like the solution in the ripple, the answer here is to create a better Dunder string method in the author model.
And now back to the browser, refreshing the page.
Ah, there you go.
Let's filter Homer's stuff.
Django has further mechanisms for you to create heavily customized filters as well, that are beyond the scope of this course, but you get the general idea how this can be useful.
|
|
show
|
3:09 |
The Dunder string method isn't the only control that you have in your model.
There is another concept as well.
You can add meta data that controls an object through the use of an inner class named meta.
Configuration here changes the behavior of queries on the object as well as how the Django admin uses it.
You can control the name of the object in the admin, the default sorting order, the name of the table in the database, a variety of permissions, what indexes are created in the database, and you can declare constraints, limiting what values are acceptable in the object.
Let's go add some meta data.
I'm back in catalogs models.py file.
I'm going to add some meta data to the book class.
This is the declaration of the inner meta class where the meta data goes.
And this is my first example of meta data.
The ordering attribute takes an iterable of field names, which if you don't use the order by clause in a query, this specifies the default sort order.
You can even sort by related fields by using the double underscore.
The code here sorts books by their related author's last name and then by the book's title.
Let's do some more.
The verbose name and verbose name, plural attributes, indicate what this class is called.
The key use of this information is the name of the class in the Django admin.
You can specify just verbose name on its own and the plural version will get an s added.
Or in the case of something like boxes where the plural spelling is different.
You can specify both terms when these are not specified.
The django admin uses the model name and the model name plus an S for plural.
Unless you tell it otherwise, the database will organize the data in question based on the order of insertion.
Generally the primary key.
If you want to search through a database by a field quickly, an index can make a big difference in performance.
To tell your database that you want an index created for this model.
You use the indexes, meta data attribute.
The example here tells the database that I want an index on the title and on the author.
This goes along with the ordering clause nicely and will give me a performance boost with my chosen default order by sorting.
Let's go look at our changes in the browser.
I've gone back out to the homepage and here its authors and instead of books its Bookington McBookFace.
Clicking on Bookington and the ordering in the change list is now ordered alphabetically by author, last name and then by title.
Dickens first, then Homer.
|
|
show
|
1:16 |
Navigating your website is based on URLs, which end up all over the place.
Different HTML pages inside of your views.
It could get messy.
If you decide to make a change to one of these, you have to make sure you find all the references.
Just like you shouldn't have hard coded strings in your code.
You shouldn't have hard coded URLs, Django fixes this by providing a way to name your URLs and then reference them.
If your URL isn't hard coded, you can make a change later without too much cost.
The only place the URL should show up as a string is in the url.py file where it maps to the view.
Convientently this is where you name it as well.
Following their own best practices that Django folks have named all of their admin URLs This means you can reference the different parts of the admin from within your own code by referencing their names.
Recall the creation of the author last name field in the book admin.
I used a callable to specify the contents of a column in the change listing.
You can go one step further and make that a link allowing you to click through to a related object.
Let's go do that.
|
|
show
|
5:03 |
And back in catalogs admin.py file.
I've imported a couple of new functions here that I'm going to need in the code Now I'll add a list display attribute to the author.
change list to make it more useful.
I've added references to the attributes in the model for id, last name, and first name and that last reference show books is going to be a callable, just like the callable author last name in the book admin class, this takes an object as an argument.
This will be called once for each author in the change list.
When the change list page is displayed, I was raised by a kindergarten teacher so there's a voice in the back of my head that yells whenever I see the wrong plural on a word.
The first thing I've done here is to get a count of all the books associated with this author so that I can properly quiet that voice.
You've seen count used before with the query manager but on its own when used on its own, it is the equivalent of counting all of the objects.
You can also call count after a query which returns just the number of items in that query result.
Programmatically this is equivalent to getting the query reset result and calling length on it.
But this is actually a lot faster.
The query manager sees the use of the count call and uses it when formatting the underlying SQL.
What you get back from the database is just the count, which is way faster than getting back all the objects and then counting them yourself, with a call to length.
The filter in this line is all the books that have a foreign key pointing to the author object passed into this call.
The reverse function is short for reverse look up and although it probably could have been named better, this is the function that looks a URL by name, That hideous string there is what the change list page for book objects in the Django admin is named.
The admin colon part is a name space for the admin module and then the format is module name, model name admin function which in this case is change list.
When an admin model is registered, Django automatically creates all the associated URLs and names them using this format.
Reverse returns a string with the URL in it.
The Django admin recognizes some query parameters to help you filter what is on the change list screen.
The chunk at the end of this line is me saying please filter the book listing showing only those books whose author's foreign keys ids match my author objects ids.
If you were paying close attention when I demonstrated the filter mechanism you'll have seen the same thing.
This is how it works, when I clicked the author filter it was using this same kind of parameterized URL.
A quick variable to represent singular or plural books.
Django has a bunch of security stuff built in to prevent you from accidentally injecting tags into your HTML.
If your author's last name has a greater than sign in it the author last name callable would seriously mess up the page.
To avoid this problem, Django automatically escapes all text output to the template engine.
Except in this case you don't want it to be escaped.
You want it to insert an actual link.
Enter the format HTML function.
This works like a call to string format but marks the string is safe to Django so that it isn't escaped when it is rendered.
Like the string format function, you use brace brackets as placeholders and then the arguments you pass it, fill those in.
There are other ways of accomplishing the same thing but this is the one that takes the least amount of code.
Alright, so let's recap this function.
This is a callable column that will show a link for each author.
If you click the link, it will go to a book change list page with a filter turned on.
The filter, filters the authors whose object got clicked.
The display text for this link will show the number of books for the author and most important to my mom, the plural will be correct.
Off to the browser to check this out.
This is the author change list page and look at those pretty links.
Homer has two books and Dickens has three.
Click one of them and there are all of the Dickens books.
|
|
show
|
1:01 |
It's wonderful that Django provides all this stuff.
It means with very little code you have a full database management tool.
Although most of the code is Django's you are still writing some yourself.
And when you write code you should write unit tests to go with it.
How do you do that?
Well, let me walk you through it.
First off, remember that you start with an empty test database.
That means your test suite will need to create a super user account programmatically.
Otherwise you won't be able to log into the admin area.
The admin area is abstracted through a class called admin site.
You can reference this inside of your tests in order to make calls to the admin area.
And if you're trying to test a customized field, the look up field shortcut will return you the value of a field as it would be shown in the admins change list page.
That means you don't have to parse the actual HTML from the view.
You can skip to just the part you want to test.
Let's go write some tests.
|
|
show
|
2:22 |
All right, a few more imports up top here than I've used in the past These are used to be able to create a user and to do that look up fields function I just spoke about.
Then because I'm testing them, I need the author and author admin models.
Seeing as I just spoke out against hard coded strings, I've added some constants here for the admin's user name, email, and password.
Before testing, I'm going to need a super user account.
I'm going to create that here inside of setup.
I'm only going to write one test so it doesn't really matter.
But this is the best practice.
Speaking of better practice, I believe in previous chapters I skipped the whole call the parents method thing.
You can get away with it depending on what you're doing, but using Super here is the safest bet.
On line 17 I'm creating an instance object of the admin site which I'll use later to reference the admin site.
The lines I just added create a new user.
Notice that a user isn't created like any other object.
The user's query manager has a special method called create user.
This is necessary because you don't store the password in plain text.
The create user method takes care of the whole encryption thing.
Unfortunately it only takes three parameters.
User name, email, and password.
This means if you want to create staff or super users you have to do that manually after it's been created.
And because these are only proxy objects, I have to save it against to write that information down to the database.
|
|
show
|
2:05 |
And here's the start of my test.
I used the client built into the test suite to log into the site using the user name and password of the admin created in the setup method.
I then verify that the response object is true.
False would mean that the login failed.
Next I make sure that nothing in my customization code is causing the page to 500 error simply by loading the page and checking that I get a 200 response.
Now I'm going to dig into the admin representation of the author object.
To do that, first I need to create an author.
Then on line 36 I'm creating an author admin object using the admin site and the author model that I created on line 35.
The look up field function takes an admin model, a model, and the name of the field to look up.
In this case I'm asking for the show books fields.
This function unfortunately is not well documented.
If you dig into the django code you'll see that it returns a tuple containing a field object, attribute name, and value.
I'm only interested in the last part.
Hence the underscores, the actual test part of this test validates that the URL query parameter is correct.
It does that by looking for a question mark author id.
inside of the looked up value.
A better test would check the whole thing but this at least gives you the idea/ And there you go, all tests pass.
Happiness is obtained.
|
|
show
|
2:10 |
I have only scratched the surface of the Django admin.
Every page you see is rendered using Django's template engine and each of the templates can be overriden.
You can make the page do whatever you like.
In fact one of my favorite third party packages is a template rewrite that makes the admin much prettier.
I'll show that to you in a later chapter.
On top of filtering you can provide search functionality and you can change and manage what queries are run, write your own specialty filters or add actions that can be performed on your objects.
In summary, the Django admin provides you with all the create, read, update and delete operations you want to control your objects with very little extra code required.
By default it is meant as an administration tool and so is restricted to the staff and super user accounts.
You create admin model objects to represent your model objects in the admin You then register these so Django can find them.
The list display and list filter attributes of an admin model allow you to control the presentation of the change list screen.
Inside of your actual models, you can use the meta data interclass to effect the default order of your queries, what your objects are named in the admin, and whether or not they're indexed.
Later on I'll dive into this more but you saw how URLs could be named and then looked up by name.
You also saw how the Django admin screens are named so you can reference them.
Some of those very same URLs take parameters like the change list screen where you can filter what is in the view.
Finally, you saw how to write your own callables inside of the admin model to fully control what is shown in the change lists columns, including using all this stuff to create links to other pages in the Django admin.
You've seen how to create a super user.
Next up, I'll show you all the users.
Spoiler alert, there's going to be a lot of password management screens coming your way.
|
|
|
56:54 |
|
show
|
1:50 |
This chapter is all about your website interacting and managing user accounts.
Django provides a lot of tools for this but it requires a bit more customization than some of the other parts.
If you're building anything with user interaction, there's a good chance you're going to need to manage user accounts.
That includes things like managing login and logo pages, controlling access to your views and managing the user data and account passwords.
In this chapter, I'll also touch a bit on customizing error pages.
Users trying to go places they're not supposed to go, result in a variety of different errors.
You may want yours to be pretty at least.
In the previous chapter I introduced you to the Django admin.
Big part of that site is user management.
Users are just a special form of data and the admin gives you control.
What about the attributes associated with your users?
The user class in Django has an attribute for user name, email, first name, and last name.
Your needs might be more than that.
To deal with this, Django also provides a couple of different ways of augmenting this data.
The first mechanism is a user profile object.
This is a model class for attributes managed like pretty much any other model class.
The only special thing about it is it has a 1-1 relationship with a user model class.
This kind of relationship means there's a shortcut from the user object to the profile, making it a short step from the authenticated user in the request to their augmented dataset.
The other alternative is you can replace Django's built in user model with your own.
There's a lot more work for this, but it can be powerful.
If you want to use email address as the login field instead of user name, for example, this is how you would achieve that.
Let's take advantage of the Django admin and create a user through the tool.
|
|
show
|
2:02 |
I'll start by visiting the admin page and logging in.
Still using that super secure password And now I can add a user by clicking the add link next to the user model line.
Note that this is a variation on the usual create model form.
Not all the attributes of the user are included on this screen.
User creation is done as a two step process.
First create the user name and password, then it goes to another page to fill in the other pieces.
I'll just create a new user name and a password.
You'll recall from creating the super user on the command line in the previous chapter that you can get around the password restrictions when you're using a management command.
You can't do that here, so I'm actually using a validated password that follows all the rules that Django is currently enforcing.
If you're interested, you can look in settings.py to change what rules Django enforces for passwords.
I'll just create the user by clicking save.
And now I'm on the second step in the process where I can fill in the rest of the user's object properties.
You've seen this before in the previous chapter, when I modified the admin user.
Let me just add some personal information for bookie.
By default, this is an active account which is neither staff nor super user.
That means the account can be used but can't log into the Django admin.
Now I'll scroll down to the bottom and save.
I'm back on the change list page and now there are two users in the user listing.
Admin created in the previous chapter and Bookie created just now.
|
|
show
|
1:11 |
Logging into a website is really about two things.
First authentication that's proving who you are.
And second is authorization which covers what you can do.
The authorization part is typically based on data associated with the account you use to authenticate.
Authorization can be something simple like is or is not staff or something very complicated based on group membership and individual permissions for pages or rows in a database.
As I mentioned in the previous chapter, the Django admin by default requires super user or staff authorization level for access.
Just because you can log in, doesn't mean you can go to all of the URLs.
Authentication can be controlled in a few different ways.
You can explicitly do a look up in the database inside of a view, checking whether the staff bit is set for example or Django provides some decorators that will check certain permissions for you.
The simplest, which just makes sure the user is logged in is called login required.
Adding this decorator to your view will take care of the permission check and will redirect users who aren't logged in to a login page.
|
|
show
|
2:22 |
I'm going to start here by creating a new app that does some things with users.
Just open the terminal and I'm going to run the start app management command.
I'm going to do that for an app called people.
The people listing now shows up in pycharm.
But before I can add some code I need to update settings.
The people apps now ready to go.
Nothing I haven't seen before.
Let me just replace the stub code.
What I'm going to do next is create a new view that lists all the users in the site.
This might be something like a find your friends function.
Before writing the view, I need some imports you haven't seen before.
The first one is the login required decorator that I mentioned a minute ago.
The 2nd one is the user object, then the usual rendering shortcut and a new one Get object or 404.
I'll come back to that one in a bit.
Now for the view this view is nice and small.
The data context contains one thing, a query set filled with all the users in the database.
The user object is a model like any other.
So finding the non staffed users is just a call to filter with the right arguments.
With the queried users in the context, the render shortcut gets called like other views I've written.
The only real new thing here is the login required decorator, by placing that over the view function.
I'm indicating to Django that you must be logged in to see this page.
If you are not logged in, Django will automatically redirect you to a login page.
It even sets a hidden field on the login page, so a successful login will send you back to this page quite handy.
The profile listing page will contain links to each user's profile page.
Well, I've got this file open.
Let me add a view for that as well.
|
|
show
|
3:09 |
Just like the profile listing, this view needs to be behind the authentication wall as well.
So it's been decorated with login required.
This view will display a single profile so it takes the id of that profile as an argument.
Let's do some basic permission management, let's say that only staff members are allowed to view a staff member's profile.
The request object, which is the first argument of every view, contains all sorts of useful information about the session.
It always has a user object inside of it.
Even if no user is authenticated, there is a dummy user for the unauthenticated case.
The user model has an attribute called is authenticated, which is true if the user is logged in.
The if statement on line 18 checks if the user is authenticated.
This is actually redundant, the login required decorator wouldn't let you get here without it But I wanted to show you the idea.
Each user object also has an attribute called is staff, which is true if the user is a staff member Line 18 checks if both of these things are true.
If so then a profile object is fetched.
The fetching is done with get object or 404 a shortcut.
It's common to want to look something up in the database and if it isn't there throw an error.
That would normally take several lines and you'll likely do it a lot So Django gives you this shortcut.
The first argument to get object or 404 is the class of the model you're querying.
All other arguments are the query arguments for the look up.
In this case matching the profile id.
If no object is found or more than one object is found.
This is a get after all.
This will raise an exception.
Django catches the exception for you and shows a 404 page.
The else block starting on line 20 is the non staff situation.
Notice that I'm explicitly querying for an account with is staff equal to false.
If someone who isn't a staff member put a staff profile id into the URL, then the second query would get run and it would fail because although the id is in the database is staff query part wouldn't match.
So to recap this view first checks to make sure you're logged in.
If your staff you can look up any profile.
If you're not staff, you can only look up profiles where the is staff property is false.
The rest of it's what you're used to the profile object that got looked up, gets put in a context dictionary and then render gets called.
Let's write some templates to go with this view.
|
|
show
|
0:58 |
You know how in those cooking shows they show you how to mix something and go straight to the oven to fetch the same thing prepared ahead of time.
Well, let's go look in templates profile listing.
What do you know it's already been baked.
There's nothing in this template you haven't seen before.
It's a four loop that iterates through all the profiles passed in and builds a list of links to the individual profile pages.
Note that I've been a bad boy here.
The URL on line 9 is hard coded.
This should be using a named value.
I'll come back and fix it later.
Yet another pre prepared casserole dish here.
This is profile.html and the template shows some information about an individual profile.
With the templates in place, let me go wire all this up in the new URLs.py file.
|
|
show
|
1:22 |
The only thing in this file you haven't seen before is on line 9.
Up until now you've been using URLs without arguments.
Line 9 defines the profile URL which maps to a profile view.
You'll remember that view took a single argument.
The id of a profile to show the angle brackets here tell the URL mapper that this is where an argument goes.
Inside of the angle brackets is a data type and the name of the argument.
The data type is actually really handy.
All URLs technically are strings.
But by putting a data type here, Django will one make sure that the URL is using a valid integer and two convert it to an integer before calling the view, so you don't have to do this yourself.
And like with the catalog app before I need to include the people URLs file that I just created in the master alexandria URLs file to register all the sub URLs All right.
I'm all set to go.
Let me start the server.
|
|
show
|
1:10 |
And I'll start by going to the profile listing and there's my user bookie.
I can click the link and see Bookies profile and that's Janice's information.
I can edit the URL Setting the profile id to one and you get the admin user's profile.
This only worked because I'm currently logged in as a staff member.
If I edit the profile again and set it to a different value, this was a non existent profile id, get object or 404 does what it says.
It 404s, in setting.py debug is set to true.
When an error happens and debug is set to true.
You get debug information like this one that shows you about the 404.
In a production situation you'll replace this with your own custom page.
I'll come back to that later.
|
|
show
|
1:01 |
Remember that I'm currently logged in as an admin.
Let me go back into the admin area and change that.
I'm going to click the log out link and now I'll go back to the profile Well that didn't go so well.
This has to do with the fact that the login page at the moment is only set up for the admin area.
There are extra steps you need to take to get normal logins working.
Like writing your own login page.
So to review the profile listing view is marked as login required through the login required decorator.
I am not currently logged in when I hit the view, Django redirects me to the log in page.
I have yet to define the login page except in the admin space and so there's nothing here and I get a page not found error.
Let's go fix all of this.
|
|
show
|
1:53 |
The login you've seen me use so far is the Django admin's login.
Django provides stubs for doing a non admin area login but they have to be completed to work.
All of this is because your site is going to look the way you want, you don't want a Django look and field but you want your own And in fact if you take a look at the admin, there's good reason for that.
Django provides you some views but you have to write your own templates for login, log out, password changes, and password resets packaged with Django is the contrib.auth module which contains some views and corresponding URLs for a variety of your authentication needs.
You can include all of these with a single line which I'll show you in a minute.
Take a look at this list.
Django provides a view for login and log out.
Then two views for doing a password change the password change view, displays the form prompting the user for a new password.
While the password change done view is what gets shown to the user after their password has been updated.
There are then four views for doing password resets, password reset shows the form for the user to request a reset.
Password reset done shows the user that a reset request has been activated.
This has a side effect of sending the user an email which contains a link.
The reset view is what is linked in the email and that link itself is a one time link where the user inputs a new password.
And then finally, the reset done view tells the user their reset is complete.
Are you ready for this?
You're going to have to write templates for all eight of these?
Nobody said user management was fun.
|
|
show
|
1:06 |
First things first, all those URLs I just talked about let's include them into our site.
I'm going to do that by opening up the master alexandria URLs.py file.
I have now included all those URLs under a sub path called accounts.
By including the contrib.auth URLs file.
This is no different than people or catalog or any of the ones that I've done before.
It's just this time I'm doing it from a Django module.
Now I need to create a template for every one of those views I just talked about.
The account views, expect these templates to be in a directory named registration.
I've pre baked most of them so that you don't have to watch me type them all out.
There's a lot of HTML coming your way over the next few lessons Don't forget that all of the code is available in the repo associated with the course.
If you don't feel like typing all this in, you can always grab it from there.
|
|
show
|
1:28 |
The first template is for logging in.
In a later chapter, I'll go into details about Django's form management tools.
For now, just know that Django has form management tools.
The login view passes in a form object to your template.
You can render that form object by calling its as p method as in paragraph tag This means it will spit out all of the HTML of the form, inside of a paragraph tag, essentially filling in this form tag in the HTML.
With the form object rendered inside a form tag.
You're almost ready to go.
You just need to submit button to make it slightly prettier, I'm using the B TN and Bt N dark CSS class from bootstrap.
There is one other piece of magic here.
Django has mechanisms built in to protect from cross site scripting attacks, without going into details, there's a token that gets passed around here and if it isn't included Django will reject the form submission.
This C S R F token tag gets replaced with the cross site reference token.
Unless you're truly interested, This can just be thought of as some magic you have to include if you forget it, Django will scream at you in the deV server console.
Unfortunately I'm not quite done yet, there's a few more things you need in order to login.
|
|
show
|
0:49 |
Let me open up settings.py and I'm gonna scroll all the way to the bottom.
The login required decorator has a hidden field that says where to send the user after a successful login, Django needs to know where to go if that field is empty The login redirect URL configuration tells Django to go to the home.
URL/ in this case.
Similarly, you need somewhere to go after successfully logging out.
The log out redirect URL configuration will also take the user to the homepage.
Now that I've got the links for logging in and logging out, I'm going to want to put them somewhere so the user can use them.
Let me open up the base template page where the Nav bar is defined.
|
|
show
|
3:33 |
The next little bit is messy unless you're familiar with bootstrap.
Quite frankly, even though I know me some bootstrap, I still find this a bit hard to read.
Bootstrap Nav bars are responsive what they show, depends on the width of the screen.
This is a good thing but requires a bunch of extra stuff.
I want to have some info on the right side of the nav along with a drop down menu with some links in it.
I also want all this to behave nicely on smaller screens.
This requires an extra step.
What I've just added here is a hamburger style menu button, that bootstrap will use if the screen is too small for all the other stuff I'm sticking in the nav.
Bootstrap is smart enough to know when to show this and when to hide it.
This is the beginning of the section that will be on the right hand side of the Nav bar.
It is a collapsible section and bootstrap knows to show either this directly or show it after the previously mentioned hamburger menu button has been clicked.
The ul tag on line 31 is what's inside of the collapsible section and it's going to list all the stuff I want to have in my nav.
The if condition on line 32 checks if the user is signed in.
If they are you get some more stuff.
Inside of that if condition is a span that says hello to the user and adds an indicator as to whether or not their staff.
The next part is a drop down menu.
Drop down menus and Bootstrap our a link plus an inner DiV to show when the menu is dropped.
This is just that link that will contain all the user's actions.
The div from line 41 to line 51 is the dropdown div.
Inside of it I've put a series of links.
The first one on line 42 is the one for the logged in user's profile page The next one is only if their staff and is a link to the admin area.
And then finally, links for changing the user's password and logging out.
Remember, at the top of all this mess there was an if tag to check if the visitor is authenticated.
If they're not, then instead you're going to be inside the else clause I've just defined in line 53 If they're not authenticated, there's no drop down and there's no hello to the user.
There's just a link to the login page.
That's a lot and some pretty messy HTML.
I can't wait to get back to Python, but first, let me demo all of this.
|
|
show
|
1:00 |
Let's go back to the profile listing page.
That's much better.
The error has gone away as I'm still not authenticated.
The login redirect decorator has sent me to the login page.
Let me login and as promised, once I've passed the login page it's automatically redirect me back to where I was trying to go in the first place.
In the top right hand corner you'll see those new changes to the Nav I talked about it says hello bookie and there's a drop down menu.
If I open up the drop down menu, it shows a link to my profile, change password and log out.
Now let's attempt to be devious and logged in as bookie, let's try to go to the admin.
The Django admin won't let me in and that's a good thing.
I'm not currently signed in with a staff account.
|
|
show
|
2:53 |
Okay, Let's take a look at the password change process.
I'll start by showing you my prewritten password change form.
Remember Django has a view for this already so you have to name the file exactly this.
The good news is you don't have to write a view.
The bad news is you do have to write the template.
Like the login page a form object gets passed into the context.
If there are errors in the form like the user has attempted something previously and been sent back to the page then the form objects error attribute will have information in it.
I'm handling that here beginning on line 5.
In the error case the forum will have some text above that indicates errors need to be corrected the div on line six.
If there aren't errors then some instructions are shown telling the user they have to enter their old password and then their new password twice.
Just like the login page, you start out with some CSRF magic.
Unlike the login page, I'm doing something a little different here.
Instead of just doing form.sp I'm using a bunch of bootstrap to make the page far prettier.
In order to get that prettiness, I'm using bootstraps class called form group.
The first group contains an input field for the old password.
This first if embedded inside the class tag checks if there are errors associated with this field like for example the user didn't get their old password correct and if so includes the is invalid class in this forms stylization.
The second if tag checks if there's a value in this field.
If so, it includes it in the input tags value attribute displaying the old value The third if tag also checks for errors displaying a div with the actual error information inside of it.
HTML is so verbose, two more input fields to go each input field gets its own form group.
So here's another form group.
This one is for the users new password.
This is also an HTML input field has similar if tags to the one above making sure that the field shows the appropriate errors, existing values and some help text.
And then this last one is pretty much the same as the previous one, except instead of being new password one, this is new password two.
The password change view will validate whether these two fields contain the same information.
Top it all off with a submit button and you're ready for the next template.
|
|
show
|
1:14 |
After the user has changed their password, Django will show them a page saying it was successful.
The view for this is built in but like everything else you need to write your own template.
That one's called, password change done.
This one's much easier.
No context, nothing to do but tell the user it worked.
My kind of HTML.
Still running the DEV server in the background here.
Let me grab that change password link from the pull down menu, and here's the form.
Oops, it didn't like my original password.
Let's try that again.
That time the new passwords didn't match, 3rd time's the charm.
Although this page is very simple, you can understand why Django wants you to write it yourself.
Writing it yourself, means it looks like it belongs on your site instead of standing out because it was a pre canned form.
It's a bit of boilerplate work but your site looks a lot better for it.
|
|
show
|
3:05 |
The password reset utility sends the user a one time link via email to reset their password.
That of course means Django needs to send email.
There's a whole module that does email stuff for you.
This is built around a main entry point which is a function called send email.
To implement the password reset.
You don't need to know about that.
Just that the built in reset view uses it.
You do need to configure Django to send email though.
Django supports multiple back ends for how to handle an email message they include SMTP.
This is kind of the obvious one.
This is how real email is sent.
In production, you would configure this to point at an SMTP server that can actually send the mail The other back ends are useful for debugging purposes.
The console back in prints whatever you would have mailed to the console in the DEV server window, this is the most useful one when you're debugging your site.
The file back end saves each message as a file.
The in memory back end keeps all the messages in an object in a module You can then access that module from your code.
This is useful if you're writing unit tests, you write test code that verifies the right content was sent by directly accessing that object in your assertions, and finally dummy is a dumping ground, you can send anything you want to it, it just ignores it.
If none of these are good enough for your needs you can write your own.
Django is pluggable all the way through.
The last bit of user account management you need is to handle password resets.
But process is multiple steps and goes like this.
The user clicks a link requesting a reset.
Typically on the login page this shows them the password reset form.
On this form they have to type in their email address.
If no matching addresses found, Django does nothing.
This is intentional.
You don't want nefarious actors knowing who has an account and who doesn't on your system by typing random email addresses into your reset form.
If a matching email is found, the reset view sends the email.
That email is based on a template that you define.
This allows you to style the reset message as well as your pages.
Inside of that email is a one time link that the user uses to actually do the reset.
After the user has requested the reset.
Their sent to the reset done page.
This isn't the reset having been performed.
This is just to indicate that the email was sent.
The one time link in the email goes to the reset form.
This is the form where the user actually creates a new password.
Spoiler alert, new password one and new password two are in your near future And finally with that all done a new password is set and the reset complete page tells the user that they've successfully performed a password reset.
|
|
show
|
2:06 |
To demonstrate the password reset, I'm going to use the console based email back end To do that, I need to add some configuration to settings.py.
All the way at the bottom again and now the email back end property tells Django to use the console back end.
With this enabled, any email that gets sent will show up in the DEV console window.
Now let's go take care of all the templates that are necessary for password resets.
Remember I'm using Django's built in views and they expect these files to exist with exactly these names.
The first step in the workflow is the password reset form.
This is where the user requests a password reset.
The form asks the user for their email address to kick off the reset workflow.
All of this of course is happening inside of our usual content block so that it maintains the look and feel of the website.
The first div here creates a smaller box to put the form in and the second aligns it in the center of the page.
This is a purely cosmetic decision.
The main part of this page is the actual form like with the others you saw this one uses bootstraps form group.
The important part is the input field asking for an email.
Also like the other forms, there are some conditional in here to display errors if necessary.
At the bottom, there is a submit button to finish it all off.
When the user fills in this form, Bootstrap has set up the HTML to validate on the client side whether or not the email address given is of the right form.
If it is then submit will be allowed.
When the user submits the form, then Django checks whether or not the user's email is in the database.
If it is, an email gets sent to the user.
|
|
show
|
1:41 |
The next template in the workflow is the template for the email that the user receives.
That's called password reset email.
Unlike the other forms, this is not being shown on the web so it doesn't extend out the base HTML file.
You can get fancier than this and do multipart my messages.
But I'm going to stick with plain text.
The entire document is wrapped in the auto escape tag with the argument off.
In the chapter on templates, I mentioned that Django automatically escapes all texts unless you tell it to do otherwise.
You don't want a random greater than sign in the text destroying your HTML.
As this text is going to include a link, turning the auto escape feature off means you'll get whatever is in the text as is.
The reset workflow provides some context that is useful when constructing the email, especially the link for the user.
It includes the protocol variable which is set to http or https depending on your site.
The domain of your site and some data for the reset link.
That data gets used inside of a URL tag.
The URL tag is used to look up the URL where the user does the actual password reset that's called password reset confirm.
Password reset confirmed is passed to arguments from the context an encoded user id and a one time user token.
|
|
show
|
0:46 |
Remember this email address only gets sent if the email given in the password reset form was in the database.
Either way, the next step in the process is the reset done page that tells the user that the reset is in process.
That's defined inside of password reset done.
Let me open that up next.
Okay, I'm back inside of HTML land here.
So this page extends from the common base and overrides the content block, like the form I have the same small box and centering piece here and then some instructions telling the user that they should get an email in their inbox to do the reset.
|
|
show
|
1:17 |
Assuming the user got that email and clicked the link inside of it.
The next step is to perform the actual reset.
That's done inside of the template called password reset confirm.
Let's look at that now.
There's a lot going on in this one but it isn't as bad as it looks.
This is the page where the user enters their new password twice.
All of this is done in a form so you can submit all that information to the server.
Like before I'm using a bootstrap form group with a whole bunch of conditionals to make the error processing pretty.
There are two password input fields here, new password one and then new password two, and of course down at the bottom is the submit button.
If all goes well and the user's passwords match and pass the password validators, then the password is reset and they're sent on to the last step.
If they're not then they'll come back to this page and error messages will be shown.
Below this there's also a little helpful error message in the case where the reset link token was invalid.
If somebody's messed around with stuff, this is what they'll see.
|
|
show
|
0:29 |
Alright, if all goes well, the last step is to tell the user it's gone well.
That's done in the password reset complete template.
Let me open it up.
This one's a bit simpler.
Just a quick message to tell the user the reset was successful and a convenience link to the login page.
The password reset complete view includes the login URL in the context to make it easier for you to write the page showing the user the link.
|
|
show
|
1:37 |
One last thing to do, let's go back to the login page and add a link to the reset process.
Once again I've used the URL tag this time, referencing the password reset view that spits out the form that kicks off the process.
Don't want any of these things hard coded.
Well now that was a lot, wasn't it?
Let me start up the DEV server and give this a whirl.
I'm going to go to the people profile listings so that I get into the login page and there's the lost password link.
I've entered bookies email address and that's the done page.
Let me switch back to the terminal and I'll just scroll up a bit.
Remember that the email system is set to the console back end.
So any email message that gets sent gets printed here instead.
Here you can see the message including all the headers and the message body and the link.
And here's the reset page and now I can enter the new password.
And there you go, your password reset process is complete.
|
|
show
|
2:52 |
You almost always run the deV server in debug mode.
You can change what mode you're in by editing the debug property in setting.py When debug is true, you get an error page with useful troubleshooting information if something goes wrong.
You saw one of those in the lesson on templates when I clicked the home URL before the page had been created.
In production, you don't want these pages, you want something else.
You don't want to expose your debug info to the black hats and you also want a prettier experience for your users.
You can create your own 404 pages but to test them you'll need to make some changes in settings.py.
The kind of changes I'm going to demonstrate can be done for 403, that's access denied, 404 page not found, and 500 help something has gone horribly awry on the server.
Let me demonstrate how to do this by creating a custom 404 page.
First though the changes you'll need in setting.py.
To see the custom 404 page you're going to need to change a couple of things to make your configuration closer to what you do in production.
First off, the debug property needs to be set to false.
Second is changing the allowed hosts property.
Django has a security feature that ensures certain http headers are set to the server hosting your site.
For this to work properly, the allowed hosts configuration has to contain the domain name or IP address of the hosting servers.
As I'm going to be running this locally, I'll add the local host info to the property.
With the configuration out of the way Now, all I have to do is the actual custom page.
The custom 404 page is named 404.html and goes in the root of the templates directory.
Here I've written an example, nothing special about this page.
It just extends from the base like home or about and overrides the title and content blocks.
I've still got the dev server running so let's go to the browser.
This looks wrong, doesn't it?
There's a reason for that because debug is set to false the server is no longer serving static files.
Static files include things like the style sheet and the logo.
So this is as good as you're going to get out of the deV server.
Let's go and do those settings, and now back to that bad URL refreshing and that's the debug page once again.
|
|
show
|
3:30 |
You've already seen the user model that comes with Django.
But what if you want to do something that the default model can't?
Well, you have three choices, if you only want to change the behavior of methods but not the underlying data structures you can inherit from user and change your configuration to point to your new object.
I've never personally done this one.
The more likely case is you have additional data you want to associate with the user Django calls this a user profile object, You create a user profile object like any other model.
The only requirement is that it has to be a 1-1 relationship with the user model object.
Because the relationship is 1-1, Django can set up references from the user to the profile and back again.
That way, if you're accessing the logged in user from the request object in a view, you're just a d reference away from your extended data.
In older versions of Django, this was the only real choice.
So it's a common approach.
But now there's a third option which is to completely replace the user object.
This requires a new storage class with some configuration to tell Django to use it and a new manager class in that you'll have to implement your own create user method amongst some other things.
There's a bit more work to get this going.
But it allows you to do fancy things like change what field is used for login If you want your users to log in with an email address instead of a user name.
This is the approach you need to take.
If you've got an existing user base switching from one of the first two modes to the third can be problematic.
It is possible, but you've got to be very careful.
Adding a profile late in the game is no big deal changing how your users log in is going to be a problem for the code and for your users.
It is best to make this decision up front and stick with it if possible.
Let's look a little further at that second option, creating a user profile object.
The only tricky part here is when the object gets created.
As the profile object has a 1-1 relationship with the user model, the user model object has to be created first.
To make this more complicated, you have to consider that different ways the user model object can be created.
You might have a self sign up page.
There is also the Django admin or you might be doing batch imports, each of these is subtly different.
You have to make sure that however, the user model gets created, the profile object gets created at the same time.
Enter Djangos signaling system, you can register a function to be called when certain events happen in the system.
One of the events you can register for is the creation of an object.
Like the user model.
There's a big exception case here.
This only works if you're using Django's code, certain bulk database activities are done in the sequel layer and won't trigger these kinds of signals.
If you stick to the Django admin and use the user models, create user method, you'll be fine.
In addition to adding event handling code.
You also want to make sure your fields are available in the Django admin.
There is a way to create sub areas in an admin page that you can take advantage of and embed your profiles fields in the user model edits page.
Let's go write a user profile.
|
|
show
|
1:49 |
I'm going to create the user profile model object in the people app.
Let me just open up people models.py.
And I'll start by replacing the boilerplate.
Alright, that's all the imports I'll need I'll be referencing the user model so I need that.
The profile is just a model object.
So the usual models.model thing is required and then the next to imports are for registering the signal.
Our user profile is going to track the favorite books of the user, so in addition to having a relationship with the user object, I'm also going to create a relationship with some books.
Hence importing catalog.models book.
The reader class is going to be my user profile.
The 1-1 field forms the 1-1 relationship with a user object.
Similar to foreign keys you've seen before, this requires an on delete flag to decide what to do if the user gets deleted.
Not much point in having a profile without the user.
So I've set this to cascade, meaning the profile will get deleted automatically if the user is.
A many to many field is a relationship that indicates this object is related to many other objects of a type.
This allows the reader to have more than one favourite book.
This field is used like a query manager.
You don't use it directly.
You call methods on it to get their relations.
For example, .all will return all of the users favorite books.
|
|
show
|
2:29 |
So that's the profile, now to set up the signal for creation.
The receiver decorator registers this function as a signal handler.
The post save value indicates the signal being listened for is the one that gets emitted after an object is saved.
The object I'm interested in getting this signal from is user.
So I set that as the sender.
Signal handler functions take one required parameter and some optional ones.
The required argument is a reference to the instance object that emitted the signal.
In our case that will be the user class.
This is necessary because you could have multiple senders registered with a single function.
I'm only interested in creating a profile, if the user object is newly created, the post save signal gets triggered on every single save.
There is a key in the keyword args called created that is true, if there is a newly created object.
There's another edge case here as well.
When doing imports from fixtures, you don't want to create the profile automatically.
The fixture is going to have that profile already, Thankfully, the raw key word will indicate if this is being loaded from a fixture.
To recap, this code will only create a new profile if the post save signal was for a user that was newly created and it wasn't created through loading a fixture.
The instance key in the key word dictionary contains the instance of the object whose creation caused the signal.
To make the code a little easier to read, I have stored that in a variable called user.
This is the code to actually create the user.
There are other cases where profile might get created for you, like on the main form of the Django admin.
As such, line 22 does one last check to see if there is a reader in the database associated with the user.
If there isn't the get method on the query manager will raise a it does not exist exception, which is great, that's what you want.
Inside of the except clause the actual profile then gets created.
If that user did exist, then the except clause won't fire and nothing will happen.
|
|
show
|
4:21 |
Alright, that's the profile object and it's automatic creation.
Let's update the Django admin as well.
Opening up people admin.py, replacing the boiler plate, just like the other admin files I'm going to need the admin module.
Line 3 is something new, I'm importing the original user admin objects so that I can extend it.
I've renamed it as base user admin to avoid confusion.
In order to register my new version of the user admin, I'll need the user object so that gets imported on line 4.
And as I'm creating an admin model for the reader object, I'll need the reader object which gets pulled in on line 6.
This is the admin class for the reader profile instead of inheriting from admin model, this inherits from stacked in line.
As the name implies, this is to create a form that is in line with another objects Admin page.
The two fields here configure the reader in line objects so that it is based on a reader object and indicates that you can't delete this directly.
You don't want user objects kicking around without associated profiles.
This is the new version of the user admin.
It extends the old one and first off it registers the reader in line as an in line form.
Now, this is where the helpfulness of the Django admin starts to get in your way.
Registering reader in line will make it show up on the create page as well as on the edit page.
If you do it on the create page, Django admin will try to create a reader for you and you'll end up with a race condition inside of that signal I showed you.
So to get around that line 16 overrides the admin models get in line instances method.
This method is what returns the in lines for the class.
On line 17 I check whether or not the object exists yet.
If it doesn't then just return an empty list.
That means there will be no in lines on the create screen.
On line 20 I'm actually returning the default which will be the in lines registered on line 14.
Got it?
One more time, remember when you create a user you put in the user name and password first and then you can add all the attributes.
The reader in line form is only going to show up in the edit form which is that form where you can edit the attributes, not where you create the base user.
The last change I have to make here is that the user object had already been registered in the admin, the old one that is.
So I have to unregistered the old one and replace it with my newly customized user admin.
The last thing to do is to migrate the objects into the database.
Let's go to the browser and check this out.
And now I'll create a user notice that the in line doesn't show up here.
The user has been created.
Now I'm on the edit page where I can fill in the attributes of the profile and here's my user profile in line at the bottom I can select the user's favorite books and save them out.
|
|
|
0:46 |
|
show
|
1:05 |
I've already created a test people file.
Let's review it.
Here are the two tests I'm proposing for the people module.
The test readers.
Signal test is nice and short Line seven creates a new user object, and line 8 asserts that the Associated Reader Object has been attached.
The 44 test goes to an invalid URL and then looks for the line from the Nabokov quote that I put in the page, and once again, all our tests are passing, I skipped over the login page.
Like I said, it would be similar to writing the 404, so I'll leave that as an exercise to you.
|
|
|
37:34 |
|
show
|
1:35 |
Up until now, almost everything has been about giving information to the user with just a few hints of accepting data.
Like a user's credentials when they're logging.
This chapter is all about getting information from your users, covering http post and HTML forms.
When dealing with data submitted from a user, you have to be careful that it conforms to your storage mechanism.
There are two tools in Django that help you do that.
The first our field validators.
These are rules you can put on a model's field about what form the data in the field can take.
Minimum and maximum values are two examples of this.
The 2nd Mechanism is database constraints.
Field validators are enforced by Django as the name implies.
Database constraints are enforced by the database.
You add metadata to your models to get these constraints in place and Django takes care of the corresponding sequel to do the configuration.
Users submit data to your server through an http post call.
To do this, you typically show them an HTML form that they fill in and then they submit.
All of this is done through a Django view.
First a view that shows the form the user fills in and then another view or the same one in a different mode to accept and process the data.
Once you've got the user's submission, you're going to want to validate it.
If the data is good, you're going to want to send the user somewhere else.
Either back where they came from or some sort of thanks for your data page.
This is often done by sending the user a redirect, telling the browser where to go next.
|
|
show
|
1:08 |
In the overview, I give you a quick rundown on the two mechanisms used for ensuring clean data in your database.
The first way is through field validators.
These are created through adding properties to a field in a model and typically limit what can be stored in the field.
You can specify limits like min and max values, min and max length, as well as validate the data conforms to a particular pattern like say email addresses, URLs, IP addresses, a list of numbers or through a regular expression.
All this validation happens when you save your model object and a validation error exception is raised if the data in the model isn't compliant with your validators.
The 2nd mechanism for managing your data formats is putting constraints on the model itself.
This is enforced by the database and is usually used to ensure constraints across multiple fields.
For example, if the combination of two fields is supposed to be unique, you can do that through a constraint.
Just like the field validators, An exception is thrown if you attempt to save something that isn't compliant.
Let's create a new app and play with some validation.
|
|
show
|
1:14 |
Offscreen, I've created a new app called review.
You've seen me do this enough now that hopefully you can manage it yourself.
As a quick reminder, you'll use the start app management command to create the app and then don't forget to add it to the installed apps configuration in settings.py.
This is my new review model.
It has both field validation and database validation constraints.
Let's start with the field validation.
On line 3, I've imported the min value validator and the max value validator from the Django core validators module.
I'm applying these two validators to the rating field to ensure that our book review can only be rated from a min value of one to a max value of five.
Every model field type supports the validators attribute which takes a touple containing one or more validation that you wish to apply to the field.
In this case I'm applying two validators.
One that ensures the minimum acceptable value is one and one that ensures the maximum acceptable value is five.
And that's all you need to do field validation.
|
|
show
|
0:54 |
Database constraints, on the other hand, go in a meta inter class configuration area.
You've seen this area before to configure things like the default sort order, the name of the model in the admin, and to create database indexes.
In this case, the constraints attribute of the meta inter class is used to add a database constraint.
The constraints themselves are found in the models module.
Here I'm using the unique constraint constraint.
It ensures that fields in this constraint are unique when used together.
In this specific case, a review must be unique to a user and a book Together, users won't be able to submit multiple reviews for the same book.
Most constraints require a name field.
This has to do with the underlying configuration of the database.
Alright, that's the model.
Let me show you the corresponding admin code.
|
|
show
|
1:31 |
You've seen most of this before.
I've registered an admin model for the review class and on line 11, I'm configuring the columns for the listing page it will show the reviews IDs.
Whatever show user and show book return.
I'll get to that a second, then the rating field, and finally whatever show summary returns So let's discuss those whatevers.
Show user is a link to this reviews user admin page.
Using reverse to look up the admin user models change list and adding the query parameter that filters the page to just that user.
Show book is the same idea, but for the reviews book attribute.
The last one shows summary is a bit different.
This is going to show a little bit of what the user wrote in their review.
This is a single line of code because Django has an undocumented feature that knows how to truncate text based on word boundaries.
If it's undocumented you might say how'd you know it was there?
Well there's a template filter that does this that is documented.
So I looked in the source code of that template filter and found this handy utility.
In the previous chapter, I created the people app to manage the data associated with each user.
In doing so I created both an in line admin for the reader object and overloaded the user admin.
As our users will have reviews.
It might be handy to see the review objects associated with each user.
To do that, I'm going to add some code to people admin.py.
|
|
show
|
2:42 |
I want to add a column to the user listing that links to the users reviews.
You've seen me do this before by adding a method to the admin object and registering it in the list display attribute.
I'm going to add a method called show reviews.
So let me start by registering it.
As the base class already defines some things in the list display attribute.
I'm just adding the new method, registration here at the end of the existing one so I don't drop off the things that are already there.
This is the code that will output the column that references the reviews for the user.
To do that, first off, you have to get all of the users reviews.
I do that by using the filter method on the reviews query manager.
Remember that this method will be called once for each row in the admin listing display with the ob argument containing the user being displayed.
I passed that obj into the filter filtering for reviews by this user.
As I'm only interested in how many reviews there are.
I then change the filter with a call to count giving the number of reviews.
This is way faster than getting the length of the result as calling count will be done in the database and only the number comes back rather than a whole query set What I'm trying to output here is a link to the listing page for the review objects.
To do that, I need a URL.
On line 21 I'm using the reverse call to look up the admin page for the review change list.
That's the listing of all review objects and I then pand a query parameter to filter just the reviews for this particular user.
On line 22, I'm determining whether or not the word review will need an S on the end.
And then finally on 24 I'm putting this all together to build the URL.
The format HTML call will build a clickable URL that is marked so Django doesn't escape it.
I'm passing the URL I looked up with reverse the number of reviews and that pesky s to get the plurals right.
With the review admin model in place and the changes here to the user, I'm all set to go and add a review.
Oop I just noticed those angry little red lines underneath review, reverse and format HTML.
They're telling me that I forgot to import them.
So let's go to the top of the file and fix that, all better.
|
|
show
|
1:55 |
The next step will be to run the Django development server and then visit the admin area.
But before I do that I want to take you on a little aside.
Up until now I've run the Django server in either a terminal window or in PyCharms terminal area.
This option is always available to you and if you're not using PyCharm, you're able to continue to do that.
Most ids have debugging tools built in.
If you're familiar with PyCharm, you'll notice I never hit the play button to run my code.
That's because the code isn't directly in Django but through the development server.
There's a way though to tell PyCharm to run the dev server when you hit the play button Not only is this convenient, but it also means you can take advantage of the debugging tools.
It just requires a bit of setup.
In the top right corner here click add configuration add new, choose Python, name your configuration and then in the script area, choose your management.py file from the project.
In the parameter section, add run server as your management command, double check that your virtual environment is pointing to the right place and then click OK Essentially what I've done here is configure PyCharm to do the same thing I would have done in the terminal by hand.
Run the management py command with run server as an argument.
Now I can hit the run button and the server will run.
If you're not using PyCharm, do a little googling to see if your ID does something similar or if you prefer the old school, keep using a terminal.
Whatever works best for you.
Now that I've run this, I can head off to the browser to use the Django admin area to add a review.
|
|
show
|
1:12 |
I'm in the admin area and I'm going to click here to add a review.
The Django admin automatically gives you a drop down with all the users and all the books, as well as fields for a rating and some text for the review.
Let me just create this here.
Oops, that's a problem.
Remember those validators, Yep, Django is stopping me from giving this book a six out of five.
Let me just fix that and there you go.
You've got a review.
The review admin shows the user, the book table, the rating, and that fancy truncated show summary column.
I can click on the user which will go to a filtered version of the show users page and if I scroll along you can see bookie has one review.
If I click that review, I'm back in the review area, filtered just by the user.
It looks the same because there's only one review at the moment.
|
|
show
|
2:44 |
Before requesting a review from a user.
You're probably going to want somewhere to show the reviews.
Up until now our books don't have pages themselves.
Well, web pages, that is.
So let's add that.
Pretty simple view here, looks up the book by ID, then renders the appropriate template.
Don't forget to add the import for the helper method.
And there's the corresponding URL.
Remember that the angle brackets here indicate that this view will take an argument.
In this case, that'll be the book's ID which is an integer, All parts of the URL are a string, so indicating that this is an integer Django will convert it for you before calling the view.
And I've added a corresponding template for the view.
The first section is the author's info.
The second section has a link for reviewing the book.
It uses the URL template tag to look up the review book view.
It hasn't been written yet.
So to get this to work, I'm going to comment it out.
And then at the bottom of the review section, all of the reviews associated with the book are looped through and displayed.
You'll recall that models with foreign keys can use the model name underscore set query manager, to access the related models.
Here that would be the review underscore set to get the books reviews and the all method on that query manager to get well, all of them.
The four loop uses the bootstrap component called a card to display the reviewer's name, rating, and review text and it also has the optional empty tag in case the book has no reviews.
With all that in place let's go take a look at it.
I've gone directly to book number one and this is the review, the author and the review underneath.
Let me go to number two.
And there's that handy empty clause in practice.
Using the admin to write reviews doesn't seem like a very good idea, especially as most of our users won't be allowed to use the admin area.
With everything now set up, you're ready to write the code that accepts a user's review.
|
|
show
|
2:42 |
Here's a quick primer on http methods, there are actually nine different methods, each of which treats data slightly differently, fetching it, sending it and requesting changes on the server like updates and deletes.
The two most common on the web are get to retrieve a document and post to submit a document.
In both cases, the browser connects via the http protocol to the domain in the URL and sends the rest of the URL as part of the header, along with the method to use.
Forget, the server responds with the contents of a web page and a status code.
For post, the request includes a body, the content to send to the server.
The response will have a status code indicating how it went.
It can also have a pages content as well.
More on that in a moment.
HTML has a concept of a form, something the user can fill in and submit.
Your typical interaction with the server.
Using a form uses both kinds of http methods.
You do a get to get the original page with the usually empty form and then when the user fills in the form, the browser uses the post to send the content to the server.
The response to the post may include content as well, which is often the case if there was an error in the form, the form gets sent back down.
If the post was successful, a common practice that I'll talk about more later is for the response to include a redirect.
That's a special status code telling the browser to go somewhere new.
So if the form submission is good, send a redirect.
That's a 302 status code.
If the form submission is bad, then send the form back to the browser and indicate where the errors are so the user can fix them and post again.
So far the views you've written have all been assuming the get method.
The actual method used by the browser is included as an argument in the request object of the view.
You could write different views with different URLs, for getting and submitting the form but you don't have to you can use the same view and change what the view does based on the http method.
The typical pattern is get to fetch the empty form for the user, post to process the submitted form, check if the form data is okay if so send a redirect or if the form data has a problem, the response will include the form annotated with the error again.
A subsequent post will happen when the user tries to submit again.
To help this process, Django includes tools for building and managing forms.
They're a little outdated stylewise.
If you use exactly what they give you, you'll get a bit of a 1990s look but you can get around that with some extra HTML.
Let's build ourselves a review form.
|
|
show
|
1:40 |
The concept of a form in Django is very similar to the concept of a model An object defines some fields.
You can write form objects to describe any form you like.
But there is a shortcut model forms.
Model forms are forms based on models, you can specify the model object and Django figures out the rest.
You'll be importing these forms so it doesn't matter where they go.
But the convention is to call it forms.py.
Let me create one of those inside of review.
To get started you need to import the model form object from the Django forms module and as I'm going to build a form around the review model, I have to import that as well.
The review form object has an inner meta class with some configuration.
The model attribute says what model to base this form upon.
If you want all the fields from your model, you're done, that's it, model equals review.
In my case, I only want the rating and text fields so I can restrict it by specifying the fields attribute.
Using this object, Django constructs a form using the information from the review model.
Remember all that validation stuff built in because the rating field is restricted to a number between one and five, the form will validate that as well.
This is why you use a model form.
It's far less work.
|
|
show
|
7:14 |
Time to write a view for the review.
Let me open up views.py.
And I've started off with the usual header stuff.
Note that I'm importing the models for books and reviews as well as that newly created form for a review.
Now the view signature.
The review book view is behind the login wall.
You don't want anonymous reviews.
And it takes an argument, the ID of the book, to review.
The first thing it does is try to find the book and it does this using the handy get object or 404 shortcut method that you've seen in previous chapters.
On line 13, I check what HTTP method was used for this call.
Everything under the if statement is for post which is for the review submission.
The first thing I do here is try to see if there is a review object for this user.
You'll recall that the review object is unique per pair of users and books.
The book in the query here is based on what was looked up and the user is found in the request object.
As this view is login required.
You know that that will be a valid user.
If the review exists, then it is used on line 16 to populate a review form object.
The fields the user submitted in the post are combined with an instance of a review object to give you a form.
If the review doesn't exist, the query manager will throw a does not exist exception.
On line 18, that gets caught and in there the form is created with just the data from the post.
By the time the code reaches line 20, you've got an instance of a form object, that doesn't mean that data is good.
The user might have submitted a rating of 47 out of 5.
The HTML will stop the user from doing that, but you can't assume that some nefarious person hasn't figured out your code and is sending you arbitrary data.
It's a nuisance, but you must always check the data on the server side.
Javascript or form validation in the browser is not sufficient as anyone can create a post and send it to your server.
The form object has a useful method called is valid.
It checks for valid data within the form.
If the data is okay, for example the validation on the rating passes then you get inside of the if block.
Normally the forms save method constructs or updates a review object saving the data from the post into the object.
The extra commit equals false here prevents that from happening.
Why would I want to stop that?
Well if this is a brand new review for example, line 18 ran then you have to specify the user and the book.
Which were not included in the post.
If you'll recall the review form doesn't even have fields for that.
There are two ways of dealing with this problem.
The first is what I've done here.
It will create or update the object without saving to the database.
And then I manually add the user and book on lines 22 and 23 then save the whole thing.
Alternatively you can create a form that has the user and book information in it and hide the fields from the user.
The info will still get sent down to the browser and be in the form and then the browser will send it back.
I prefer not to do it this way because it means you then have to validate that the values of those fields correspond to the user's actual user ID.
So that's why I used this method.
Finally once the review object has been saved the browser needs to show a result.
What better than to send them to the page where they can see their review.
It's bad practice to hard code a URL When adding the password reset link to the login page you used the URL template tag to look up a URL by name.
Did you think you could only do that for Django URLs?
Of course not.
You can name your own.
I'll show you how to do that in a minute.
The redirect function is a shortcut that returns an http response object with a 302 status code and the URL being redirected to.
The arguments here to redirect tell it the name of a URL to look up and the arguments for that URL.
In this case it will find the URL named book.
Okay that's the good case, but what if there's an error in the form?
This new chunk of code is the bad case.
I'm still using the review form object based on the post data sent.
But now I want to re render the review form page.
When the is valid method of the form object is called Django automatically annotates the form object with any error information.
This re rendered page will be able to show the user indicators of what they messed up.
In our case, if the user was well behaved, they won't be able to mess anything up because the only validation is the rating and if they're using the form that will be correct.
But you shouldn't assume anything about your users or their behavior.
So that's the submission part.
This might feel a little backwards doing that first.
But now let's see how to spit out the form, the get part.
You should only get to line 36 if the HTTP method was get post was handled in the previous if block.
Actually that's a bit of a white lie.
You could also get here if the method was delete or something else.
But this view isn't handling those cases at all.
So you can just treat anything but post as if it's a get.
Unless you're writing a rest interface or something like that.
You can usually just stick to get and post and ignore everything else.
Where was I?
Right, So this code is for http get.
Hence the comment on line.
35.
It's kind of similar to the post case in that the goal is the creation of a review form object.
This time you don't have a post to pre populate the form though.
It will either get pre populated with an existing review like on line 38.
Or if there is no review for this user book pair, you'll get an empty form on line 40.
The rest is just your typical packet in a data context and call render sort of thing.
Let's register this view as a URL.
|
|
show
|
0:46 |
Here's a review URLs file that I built previously, you've seen most of this kind of code before.
I'm registering the URL for the view.
Remember that it takes a book as an argument and nothing special has to be done for the whole post thing, it's all the same.
The only thing that's a little new here is the name argument.
This is how you name a URL.
Remember calling the redirect function in the view.
Well, the name you pass it is this name, easy enough.
Seeing as this is a new URLs file, I have to add it to the global declaration in Alexandra.URLs.
All done.
|
|
show
|
3:16 |
With the URL in place, the last thing you need is a template for the form.
There's the ugly and easy way to do things or the pretty and much more coding way.
I'm going to do the second briefly though I'll describe the ugly way.
the form object has a couple of methods that spit out HTML for the form.
There's no easy way to apply any sort of styling you're stuck with the HTML Django gives you.
The hard way is Django doesn't care if you don't call that method.
You can write your form by hand as long as it has the correctly named fields and is wired to the correct URL, it'll still work.
Let's go do the hard way.
Once again I've been busy offscreen and there's review.html.
The form object has a dictionary inside of it called errors which will be populated with any errors in the form.
This first chunk of HTML will put an error message at the top of the page, if the form has errors in it.
Next I declare an HTML form and just like the login form, this needs the CSRF safety token.
Then I'm using bootstrap's classes to make prettier form fields The form object has an object inside of it for each field.
Each field has a label tag method that will spit out an HTML label tag.
I could write this by hand but it doesn't need any extra styling so I'm just embedding the label in place.
The first input field is for the custom rating value I'm explicitly setting the range here of min and max so the user can't easily make a mistake.
Then the listing of classes of the field changes depending on whether or not there's an error.
If there's a rating errors is invalid is added to the class list to change the styling of the field.
Similarly, the value for the field changes based on the value in the form object If this page is being viewed from a get then the rating value will be empty and so I set it to a default of five.
If the page is being viewed from a post, there will be data from the previous view of the page and hence some content in the form rating value attributes.
And then at the bottom of this first form group I have a little area that shows any error information for this field.
The second form group is similar to the first but this time it's a text area for the review text content.
A quick little gotcha here.
The placeholder attribute on a text area only works if the text area is completely empty.
Make sure you don't put any spaces between the opening and closing text area tags.
Those are the two fields.
So the last thing you need is to be able to submit the form.
At the bottom here, there's a button that does just that.
Okay, you're almost all set the view is in place the templates written and you've got your form going.
You may recall I commented something out in the book template.
This URL is now valid so I can get rid of the comment.
Alright, let's go write a review.
|
|
show
|
0:21 |
I'm back at the page for Oliver twist.
This is the link that got an commented.
Let me click it.
Here's the form.
I'll just fill it in and there you go.
The review has been processed and I'm back at the books page, which now shows the review.
|
|
show
|
1:24 |
With the review in place and working.
Let's do a bit of tidying up using that new named URL thing.
Here I've added a name to the book listing URL.
Next I'll update the template that goes with it.
Here I've made each book title clickable to go off to the corresponding book page.
Let's go back to the browser and check this out, and there you have it.
The book listing now has links to each book's page.
Let's click through.
There's Oliver Twist and his review and you'll recall that the review view process was capable of editing a review.
Let's take a look at that now by clicking the review button.
The same get is done with the form but this time it's pre populated with everything you need.
Change of value and save it.
And the books review has been updated.
The same view using both get and post has managed not only to create new reviews but edit them as well.
|
|
show
|
3:43 |
You wrote some code, time to write some tests.
A few things to think about with the new features.
First you'll want to test a view that is behind the login wall, which means you'll have to log in.
Next you're going to be accessing pages and just like in the templates you shouldn't hard code your URLs.
So you'll use reverse in the test to look them up.
You've seen the test client for getting a page.
Well it also supports posting and to properly test your form process.
You'll want to verify that you get the correct redirect on successful form submission.
Once again I have pre prepared a file for this.
Here's test review.
To test things behind the login wall you're going to need a user or two and as the reviews are per user.
I've created a few of those as well.
The setup method gets called before each test and this one creates a list of users who are a named user dash number all with the same password.
To test a review, I'll need a book, to get a book I need an author, so lines 22 through 24 create an author and book.
The first chunk of this test makes sure that a book page that has no reviews comes back saying that there are no reviews.
On line 27, I used the reverse method to look up the URL named book with the book ID as an argument.
I then use the client get method to get that URL and assert that it came back as 200 status code, with the no reviews yet string showing up in the content.
The next chunk tests that the review page is behind the login wall.
I look up the review URL and hit it.
I'm not logged in so I should get redirected to the login page.
This is always a good test to have.
It's easy to forget or accidentally delete a login decorator and you'd be exposing a page you didn't mean to.
So validating it in your tests so you can do regression is a good idea.
The third part of this test tests the submission of a review.
Lines 39 through 42 create a dictionary.
That will be used as the posts submission.
Line 44 logs the client in while the line after does the actual post.
The post is to the URL that was looked up earlier and the data being sent is the equivalent of a form submission.
As this data is valid, what should come back is a redirect corresponding to the book's page.
I checked the 302 status code that's redirect and I checked that the URL inside of it is correct.
After that online 49 I fetched the first review object associated with the book.
As there should only be one, it should be the right one.
I then checked that the rating and text was set correctly from the form submission.
Not satisfied to do this once, on line 53, I do the process again using a different rating and review text.
Next I test the book page.
On line 67 I log out.
I don't technically have to do this, but this page is not behind the login wall so it doesn't hurt.
On line 68, I get the book page then validate that there are two reviews on the page, one from each of users, 0 and 1.
Finally, I double checked that those were the only two reviews in the content.
More code, more tests, and everything still passes.
That's great.
|
|
show
|
1:33 |
You've now learned how to submit data through web forms.
Let's review the chapter.
You can restrict the values of fields in models two different way.
Either through validation arguments or through model constraints.
Field validation is done by Django at the save step.
Whereas model constraints are enforced by the database.
Data is submitted to your server using the http post method.
And the most common way of doing this is through an HTML form.
The request parameter to a view has a method attribute that indicates what http method was used for the call.
You can use that to write views that take both get and post.
You also saw how to use model form objects as a quick way to create a form based on your Django models and you use them to do form processing.
Most form processing is done in a four step process.
The user hits your view with a get method and retrieves the form usually empty.
Then they submit the form with their data using post.
If the data is good they are redirected somewhere else or if the data is bad they're sent back to the form with indications of their errors.
Finally, although you've seen naming a URL before for system URLs, I showed you how to name them yourself and look them up in your code.
Posting data isn't the only way of getting stuff to your server.
In the next chapter, I'll cover how to upload files.
|
|
|
28:41 |
|
show
|
0:47 |
In the previous chapter, you learned how to handle information sent to the server through HTML forms and the http post method.
In this chapter, you'll continue on your journey of sending data to the server through uploading files.
The focus of this chapter is uploading files like other submissions.
This is done through an HTML form and an http post.
Just with special specific fields.
So to get a file to the server, you'll first need to understand how to process upload forms.
Django calls, uploaded files, media files and handles them distinctly from views, static files or templates.
You'll learn all about how these files are handled and How if you're not careful, you can run into some dangerous situations.
Users can be a tricky bunch.
|
|
show
|
1:13 |
Up until now, you've been dealing with text data submitted via a form.
Even your numeric data has technically been text data.
When you submit the review rating field to the server in a form, it is sent as text.
The Django model form knows the field types of the Associated Model so it automatically converts the text sent by the browser into the appropriate storage type, like an integer between 1 and 5.
Forms can also send files which includes a way of sending binary data like images.
Django calls these uploads media files, they are handled separately from a view page or static files.
To use media files, first off, you need to configure your system to tell Django where to put these uploaded files and of course you need a directory to put them in.
It is recommended that this be outside of your code base.
This allows you to do things like set your code is read only on the server so if someone malicious gets in it limits the damage.
There are model fields that can point to media files and a very commonly used one is the image field, which expects an uploaded file to be an image.
The only caveat with this is it requires a third party image processing library called Pillow.
|
|
show
|
2:08 |
To demonstrate uploads, I'm going to add the ability to associate a picture of an author with their model object.
To do that, I'll be adding an image field to the author and configuring Django to allow the ability to upload those same files.
First off, let's edit the author model to add the image field.
That's pretty much it from an object perspective.
It's simply a new field.
Note that I'm allowing it to be both blank and null so that the field can be empty in forms and null in the database if I did otherwise, the picture would be mandatory.
Which in addition to being a pain for adding an author would impact existing data in our database, because this field is allowed to be empty.
The migration process is simple and Django will set this field to empty for any existing author data.
More on this in a future chapter on database migrations.
Those files that I want to upload have to go somewhere and you have to tell Django where that is and what URL to serve them under.
Let me modify settings.py, scrolling all the way to the bottom.
The media route configuration tells Django where to put uploaded files.
The pattern I usually use is to go up one directory from the Django project.
Hence the parent attribute, then in a folder named uploads.
Then in a folder named after the project, I do this so that if I'm hosting multiple projects on the same server.
I don't have to worry about file name clashes across the projects.
The media URL configuration tells Django what you are able to use when serving these files.
This is similar to the static URL concept you've seen earlier The next change I'm going to make is inside of alexandria.URLs I'm going to do this so that the DEV server can serve the media files.
|
|
show
|
1:29 |
Unlike with static files, the Django development server will not serve media files by default.
Like static files, you shouldn't be using Django to serve these in production but you should be having your web server take care of it for you.
This is also why they tend to get served under a different URL path so you can mount it separately in Apache or nginx or whatever you're using.
The DEV server not serving these files is a pain if you're debugging so you can get around that by explicitly serving the files.
I've done that here with these two new lines.
First I check if the server is in debug mode and will only serve the media files if debug is true.
If debug is true, then I use the static function from the static module to register the media directory, like a static files directory.
using the recently added media URL and media route configurations to tell static where to find those files.
Remember, you should only do this in dev.
This is dangerous for several reasons, I'll get into that more in a bit.
The squiggly red lines are telling me that the imports haven't been done in this file So let me do that now, with the code and settings out of the way let's go create those diretories and migrate the programs.
|
|
show
|
1:00 |
First off, I'll create the directories that I told setting.py would exist.
Now, I'll create the migration file for the changes to author.
Oops.
Remember when I told you that image field requires a third party library.
Time to install that now.
It's called pillow and it's an awesome image manipulation library.
If you haven't played with it before, I suggest going and taking a look.
It is really handy if you want to do image thumbnails and the like.
Django uses it to validate that a file is actually an image and to support certain metadata, like the image dimensions.
With that installed, let's try to make migrations again.
That's better now to migrate and you're good to go.
|
|
show
|
1:19 |
In the background, I fired up the DEV server and here I am in the admin, I'll go to authors and add a new one.
The Django admin knows what to do with an image field.
So when I add Ernest here, I can upload his photo.
Thanks to Wikimedia commons for the image source, which is in the sample codes, data directory for your convenience.
Let me just save Ernest and the author has been added.
Let's go look at the uploads directory to see what happened when I created Ernest.
Great.
You've got an uploaded image or is it great?
Note, that it is named the same thing the user that, that would be me in this case, named the file on their computer.
What would happen if someone else came along and named their arthur Conan Doyle picture Hemingway.jpeg?
Well you'd overwrite the file and because the Hemingway image field merely points at the file name, you'd have an incorrect Hemingway bio.
Let's talk about what can be done about this.
|
|
show
|
1:10 |
The concept of the Django admin is you're trusting the people using it because their administrators.
When you write your own upload pages, you don't have to name the file you create on the user's behalf, whatever it was, the user named it.
This is a good thing.
Typically if you let random people on the internet name things, it can result in strange choices.
Google Boaty McBoatface if you don't believe me.
One way of managing the file overriding problem is to make sure that either the file name or the directory the file is in on the server has something in it that is unique to the user.
I often use the users ID.
Their numeric ID, not the user name.
That way you can support a user choosing a new user name without having complications.
The next step is to write a view that allows the uploading of binary data, like image files.
This is done using an http post method like all forms.
It just requires a special HTML form field.
The upload view is then responsible for taking the binary data out of the form field and writing it to the server.
Typically, somewhere under the media route you specified in settings.py.
|
|
show
|
5:05 |
I'm going to add three new views.
The first will be for showing a list of authors.
The second for showing an author's details and the third will be the actual image upload view.
As all of this is to do with the author.
I'm going to put it inside of catalog which is where the author's model object exists So opening catalog views.py.
First off some updates to the import section of the view file.
When I get to the uploads view, I'm going to need to do some file manipulation.
So up here, I've added the pathlib library.
I'm also going to need the media route setting and I'm going to want the upload to require a login.
So there's a couple more imports there as well.
After an upload you want to send the user somewhere.
So here is the redirect function you used in the previous chapter on a successful form upload and the final import change is I'm going to need the author model.
The first new view here lists authors.
It's pretty much as basic as a view can get query all the authors in the database ordering by their last name, then render it in a template.
The next view is for a specific author.
This is similar to book listing and book in a previous chapter.
You now have corresponding author listing and author views.
Okay, now, this is what you came here for, file uploads.
Like the views you saw in the previous chapter.
First off, I'm going to need to display the form associated with the upload the get renders the form for the given author that form will have a special HTML field for attaching a file and like any form will be submitted via http post.
This is the code for managing the post.
All uploaded files are included in the request object in a dictionary called files, all caps.
Each upload field in the form has a name which is the key in the files dictionary.
I've called mine author photo.
Line 42 just stores a reference to that object.
Line 43 constructs the name of a path, to save this binary data into.
As I mentioned before, one way of handling name conflicts is to use a unique identifier.
I'm going to put the file into the media route and instead of accepting what the user named it, I'm going to name it.
The user's ID number underscore whatever the user wanted to name it.
This is actually not the best practice.
Depending on your server you might get in trouble.
The user might include characters in their file name that can cause problems.
For simplicity's sake, I'm going to stick with this for now but you might want to use a fully generated file name with a unique number instead.
Lines 44 through 46 write the file to the disk on the server.
Line 44 opens a new file in, write binary mode and the four loop writes chunks of binary to the file a piece at a time.
The chunks method on the upload object has smart defaults about how big each part of the file should be.
So you're using a good buffer instead of writing, say, a single bite at a time.
Lines 48 and 49 update the author object with the path for the newly created file And then, of course, don't forget to save after editing the model object.
And then all that being done.
Line 51 uses good old redirect to send the user to the author's page so they can see the newly uploaded file.
|
|
show
|
0:56 |
You're going to need three URLs for these three views.
So I'm going to open up catalog URLs.py, nothing you haven't seen here before.
Three URLs, each with a name attribute so that they can be referenced elsewhere The author and upload author photo URLs taken integer argument, which is the author's ID.
With your three new views and your three new URLs.
All that's left is some templates, luckily I have those for you already.
|
|
show
|
3:13 |
First the author listing.
I'm keeping it pretty simple here.
The view sends in a list of authors in the context and I loop through them providing a link to the author's corresponding view and their last name.
Note that I'm using the named URL that I just created for the author.
Clicking on that link will take the user to the author's bio page which is in author.html.
This is the author specific template.
The top block here contains the author's bio information, Then there's a paragraph containing their picture, if there is one.
Note how I'm accessing the photo on line 11.
The author's image field is named picture.
That field has an attribute called URL, that returns the URL used to access the file.
There are other attributes as well, like what the file is named if you want that.
I've applied a bit of styling to make sure that the image doesn't overwhelm the page by setting max height to 250 pixels.
Inside of the else block, if there currently isn't a photo and the user is authenticated.
I provide an upload link.
This uses the URL tag to look up the upload author photo URL and renders the length as a bootstrap button.
The chunk at the bottom uses the book set query manager for the author object and loops through all the author's books, listing them and providing links to the book detail pages.
One last template for uploading the author's photo.
This is the one you came here for the upload form.
Nothing much different here, the only thing you have to do is make sure to use the right kind of HTML form field.
That's the file input field type shown here on line 16 and 17, two things you need to make sure you get right here.
The name and the ID arguments have to match up to what you're expecting in your upload view.
You'll recall that in the view the requests.files dictionary key was called author photo.
So here in the HTML form you need a name field that is the same.
And depending on the other tools you're using, it's good practice to also have an ID field.
Quite honestly, I forget when you must have both and when you only need the name field and rather than look it up every time, I've just gotten the habit of providing both of them every time.
Unfortunately, bootstrap does not provide a stylized upload button.
The default one is very 1990s and kind of hideous.
There are loads and loads of javascript tools out there to make this prettier and do things like Dragon drop support from your file Explorer.
But for the purpose of learning about uploads, I'm going to keep it simple.
Ugly but simple.
I'm going to make one last change which is an edit inside of home.html.
I added a link to the author's listing on the homepage Just to make it easy to find.
Let's go try this out.
|
|
show
|
0:39 |
Here's the homepage with our new author listing link and there's the author's page.
I'll start by clicking on Ernest and there's his photo.
Going back let's click on Homer, and here's the upload author photo button.
There's our ugly upload button, a stately looking homer, and redirected back to the author page.
There he is, complete with his books.
|
|
show
|
3:48 |
I have yada yadaed a whole bunch of stuff in this chapter.
I've tried to concentrate on how uploads work rather than all the trouble you can get into when dealing with user files.
I mentioned that the first problem you have to deal with is that the user has named their file.
You need to make sure that your code doesn't allow users to overwrite each other's file contents.
You should also be careful with what characters are allowed.
The user's operating system might not be the same as your servers and even without malicious intent, the valid list of characters for their os might not match that of your servers.
One possible mechanism here is to just name the file something numeric that can add its own challenges.
You might not want to just completely rename it.
You might want to keep the file extensions so that when you're maintaining the server, you can tell what kind of file it is.
That of course means having to do some manipulation based on either detecting the file type or trusting what the user used as their file extension.
I'm not big on trusting the user for anything.
Django does go so far as to prevent the use of non image files in the image field though, so some protection is there, if you're just doing pictures.
A common pattern is to have two fields for each file One that is the file field and the other which is a text field containing the name, the user gave the file.
This distinguishes what you want to safely call it without losing what the user called it.
This may or may not be important in the case of an image for an author the user doesn't need to be aware of what it's named on the server.
In the case of a series of files in something like Dropbox, you're going to want to allow the user to name the file what they want.
If all that isn't complicated enough.
All media files are public.
If you know the URL you can see the file.
If you can see one file like the one you uploaded, you can probably take a guess at what the names of other files are.
Depending on your web server's configuration, you might even show a listing of the files that are uploaded.
Yet another reason to let your web server manage this stuff.
You don't want to be writing all that in your Django code.
There are ways around this but they're messy.
I'll come back to that in a second.
Like static files, media files shouldn't be served by Django directly in a production environment Your media file directory can be mounted by your web server.
Be careful here, it needs to be rideable by your Django instance.
Both Apache and nginx have passed through mechanisms for serving private files.
This would be that messy thing I mentioned before.
If you want to require login for accessing media files, you can provide a view that is login required, does whatever checks are necessary, and then calls down into the web server by setting an http header.
If done correctly, the web server will then serve the file out of a private area, but only if you've got that header set.
The details of this are beyond this course.
Google Django protect media files and your server name for details.
Whenever dealing with user data, you must assume the user is out to get you and will attempt to do horrible things to your server.
This goes doubly for media uploads.
I've already spoken about problematic file names and how to get around them.
These don't tend to be too much of a problem if your web server is configured correctly, but bad characters or relative pathing in the name can cause problems.
The other issue, of course is problematic content.
If you ran a site like this book site, you'd likely eventually get adult content as the author's photos.
You have to consider who sees what content in comparison to who owns it and what to do in cases where people are being jerks.
I'm told the internet's full of them.
|
|
show
|
4:51 |
It's that time again, boys and girls.
Let's figure out how to test all this new code.
Your test suite isn't going to actually upload a file.
So how do you test an upload view.
Well, Django has an answer to that.
It's the simple uploaded file object which mimics everything you need for testing.
Managing tests is often about more than just managing the test code.
You often have to manage data to go with it.
Up until now you've been creating users and books inside of the tests.
You're not going to want to do that with images.
You're going to need a folder somewhere with some data that you can use to do your tests.
You should manage that data the same way you manage code.
It should all be part of your code repo.
I've just pasted the usual imports and set up here.
You've seen all this kind of code before.
Now for the test, I'm starting out by getting ready adding an author and getting the relevant URLs that go with that author.
The first thing I'm testing is to make sure you don't see the upload button on the author page, if you're not logged in.
At this point, I'm still not logged in and I'm making sure that you get redirected if you try the upload page.
Now I'm logging in and going to the upload page, and this is the code that actually tests the upload.
I open up an image file from the test data directory, create a simple uploaded file object passing in the file I just read and then use that in the posts form dictionary.
An accepted file should result in a 302 going to the author's page.
Now I'm refetching the author to check their picture field.
This step is important.
Remember that these objects are just proxies to the database.
If I don't re fetch I'll have a stale copy the one from when I created it.
The view will have changed the database but the original from a few lines back won't see that.
Refetching the author fixes that problem.
With the new copy, I validate the name of the associated picture file using the user ID naming convention from the view.
Last I do a bit of cleanup removing the uploaded file.
There are better ways of doing this.
If the test failed between the upload and this cleanup, you'd end up with this file sticking around.
Better way to do this would be to create a special uploads directory just for the test and use the tear down method, the pair of the setup method to clean up that temp directory.
I'm a bit lazy though and haven't gone through all that.
I did want to put the file delete code here though so that you're aware that a side effect of the test would be something in your uploads directory.
The rest I'll leave as a homework assignment or you can be lazy like me and just delete the files manually, if something goes wrong.
Offscreen, I created the data directory and copied Michael.jpeg from my samples in here.
This is the photo that gets used in the test And there you go.
I'm up to nine tests and thankfully they're all still passing.
|
|
show
|
1:03 |
This chapter has been about uploading file content.
Just like any other submission, you use an HTML form, but in this case you use a special field, both in the form and in the requests object to get at the file.
There are three high level abstractions of content in Django.
A page through a view.
A static file, like a style sheet, or media files from uploads.
Media files should be uploaded to a directory outside of your code.
The name of the file on the server doesn't have to be the same name the user gave it.
And in particular it is best practice not to trust what the user named it.
At bare minimum you probably want the users ID in the file path so that one user doesn't overwrite another user's file of the same name.
And finally I got all paranoid telling you the internet is full of jerks and you have to be very careful when accepting content from said jerks.
That's it for uploads.
In the next chapter, I'll be covering how to write your own Django management commands.
|
|
|
16:48 |
|
show
|
0:31 |
In the previous chapter, you learned how to deal with uploaded files.
In this chapter, I'm going to show you more about Django management commands, including how to write your own.
I'll start this chapter out by reviewing the jango management commands you've seen so far, then I'll introduce you to some other commands that you may find useful.
Of course, you may run into a situation where what you want to do hasn't been done for you so you can always write your own.
And finally, as with all the chapters, I'll show you what you need to do to test the concepts you will learn.
|
|
show
|
1:31 |
Django provides you a command line entry point in your project through the manage.py script.
This script has many subcommands which help you manage your project and the data within.
Some of the commands you've seen so far are start project to create the cookie cutter parts of a new project.
Create super user, which does exactly what it says.
Start app to add a new Django app to your project.
Make migrations and its companion migrate to manage your database schemas.
Run server to run the Django development server.
So you can debug your creation, dump data and its companion load data to export content from an import content to your database.
Shell to get a Python rebel.
That is Django project aware.
DB shell as a shortcut into your databases, rebel equivalent and test to run your test suite and you are writing tests right?
Some other commands that I haven't touched on yet are check to run the system check framework.
This is similar to the check that is done when the development server starts up.
Diff settings shows the difference between your settings and the default ones.
Test server is an alternative to the development server that creates a test database and optionally populates it with a fixture and change password allows you to manage a user's password, essentially giving you the ability to do resets even if you don't have the Django admin installed.
|
|
show
|
1:09 |
I've mentioned before that the Django core team has a practice of building the library in applicable fashion, allowing you the programmer to use the same mechanisms as the module developers.
Django management commands are no different by placing a Python script in the right location and inheriting from a class.
You can write your own management command in only a few lines of code.
Management commands are contained within an app and must be inside of a nested directory called management.
Then commands, this always feels to me like they had bigger plans for this, you end up with a two level directory structure on only ever using the bottom folder, but as strange as it may be, this is how it works.
By putting your script in this directory, it will automatically get picked up.
It will show up in the listing of commands when you run manage.py without any arguments.
The name of the command will be whatever you named the script file.
Inside of your script file, you need to create a class that inherits from base command There are a couple of key methods you need to define to get things going and the system uses argparse to pass arguments into your command.
Let's go write some code.
|
|
show
|
3:45 |
I'm going to write a couple of commands inside of the catalog Django app.
The first thing I have to do is create the necessary folder structure.
This is the parent as this is a Python module, you need a dunder init file here, and then the folder where the commands go Also needs a dunder init.
Now that I've got the folders ready, I'll create a new file called authors.py.
To start off, I need to import base command, which my command class will inherit from.
As I'm going to be playing with author data, I need to import the author model class, and here I've defined a command.
It can be named anything as long as inherits from base command.
I can optionally add a help attribute to the class and this will show up in the command line help info.
Right out of the box, you get a --help for your command without having to do much besides write this sentence.
And this is the key part.
You need to override the base classes handle method.
This is the entry point to your code, when your command is run.
Django will instansiate your object and call this method, passing in arguments and options from the command line.
More on those later.
This is the beginning of the code for my command.
This command is going to show information on the authors in the system.
I'm starting out by iterating over all the authors and creating some strings with author info appending them to a list which I'm keeping in the variable named output.
Here, I'm adding more info if there's a picture or a message that says there isn't one if there is not.
And finally I just output this to the screen using standardout.write.
Note that I haven't used print here.
You can, but you shouldn't.
The base command provides hooks into the standard out and standard error streams.
It does this so that you can do funky things like control where they go.
For example, when you go to test this, you may want to ask the command to capture standard out for you.
If you used print, you'd have to do this on your own.
If you stick with the streams provided by the base class, you'll have hooks into the streams.
Let's go run our commands.
|
|
show
|
1:29 |
I'm inside my trusty terminal.
Let me start by running the management command without any arguments.
Let me just scroll up here.
Remember if you're on Windows, you'll have to be less lazy than I and type out the whole Python manage.py to get this to run.
The result of managed.py without a subcommand is a list of all of the commands grouped by the app that defines them.
The second grouping here is the catalog app and the newly minted authors command.
Okay, now that you've seen that, let me run the command itself.
Calling the authors command prints out a list of the author's last name, first name, if there is one, birth year, and some info about their picture, file name, remember how I said there was help.
Let's see that.
There's a lot here and that's because the base command is providing all sorts for you automatically.
You can manage django settings, you can modify the Python path, you can decide whether or not to use colorized output.
All that comes for free by inheriting from base command because there's so much help info it would be easy to miss this line that I've highlighted, but it is that help attribute that you defined in the class.
If your command had arguments, additional information about them would show up here as well.
Let's go write just such a command.
|
|
show
|
3:59 |
The next command will give info about a book.
I'll call it book, like before I need to create the file in the commands subdirectory.
The top of the file looks very similar to authors.
I'm importing the book the base command and creating the command and specifying the help.
I want the book command to take arguments so I'm going to override the base commands add arguments method.
The add arguments method takes a parameter which is an arg parse parser which I can then use to add command line arguments.
Arg parse is a pretty deep library.
I won't get into it here but anything you can do with it, you can do with your management command.
In this one I've added two arguments, the first one is --all the store true action indicates that a bullion should be created in the options dictionary called all and set to true if the user gave this flag.
The help parameter is the argument specific help message.
The second call to add argument specifies that there can be several arguments.
It's called book IEDs and the narg equals star means to expect multiple arguments, all of which will be stored in a list.
By setting the type to int arg parse will automatically take the command line contents which is always text and convert it to integers.
If you pass in something that isn't an integer, it will complain.
That means you don't have to check any of that stuff yourself which is nice.
As with --all, the help parameter here is an argument for specific help for the book IDs argument.
With all the arguments added, now I have to do is write the logic for the command.
Like before I'm going to override the handle method as the entry point.
The options dictionary will be populated with any arguments that were defined in the add argument method.
The first thing I do here is check for the --all flag and if it's there, I'm populating a books variable with all of the books in the system.
If the --all flag isn't there, then I look at the book IDs list and create a filter of books based on that list.
If everything is normal at this point in the code, you'll have a query set result containing some book objects.
In the case where the user didn't give any of the expected arguments, you can raise an exception.
The command error exception is handled by the base command and will properly output an error message.
This won't trigger a trace back.
And finally, just like the author's command, I iterate over some objects.
Books this time sticking the info in a list, then write it all to standard out.
Let's go look at book command.
|
|
show
|
0:48 |
I'll start with no arguments at all and there you go.
That's the command error exception.
I can use help to find out more information.
Let me just scroll back again here.
Note that you get the help text from the class and some help for the positional book IDs and the --all command gets mixed in with the other options.
You've got help on all parts of the command.
Let me scroll back down, and there's the --all.
Now how about with the filter and there you go command line goodness.
|
|
show
|
2:40 |
The management module includes a function for programmatically calling a Django command.
This makes testing fairly straightforward.
Remember when I said don't use print well If you used standard out as I suggested, you can take advantage of some output capture when you're testing and with errors you can use the assert raises mechanism to check that command errors get raised at the appropriate time Let's go look at some test code, I've taken the liberty of writing some test code ahead of time.
Let me just open it up.
This first method is a helper, call command on line 14 is a function allowing you to programmatically invoke a Django command.
It takes the name of a command to run along with the arguments and options that would have been on the command line.
One of the supported options that is built in is the ability to provide a buffer for standard out and or standard error.
On line 12, I'm creating such a buffer, it gets added to the options dictionary and then after the command is invoked, the contents in the buffer are returned from my run command helper.
So when I use the run command method it will invoke a command and return what output the command caused allowing me to assert on the response.
Line 17-23 are some setup code putting an author and some books in the test database.
Lines 25 through 28 test the author command.
It uses the run command helper and checks that the author's last name shows up in the output.
Lines 30 through 44, test the book command by passing in different arguments to run command I'm setting the contents of options, mimicking command line invocation.
Using all equals true is testing the --all argument and it's done in the same way as the author test above.
Call the run command and then validate the output.
Similarly here but instead of passing in all equals true.
I'm passing in a book ID as an argument to get a single book out And then finally you'll recall that passing in no arguments raises a command error.
So line 43 creates a context block that expects that exception.
If it happens as expected, then the test will pass.
All good and a bucket of chicken.
|
|
show
|
0:56 |
You've been using Django management commands throughout the course this chapter reviewed some of the ones you've used as well as pointed you at some others.
Then I showed you how you can write your own, you do that by inheriting from base command in a script file that is in the management commands subdirectory of an app.
Your commands entry point is the handle method and you can use the add arguments method along with arg parse to define extra arguments for your command.
And of course a quick reminder that you should use base command's standard out and standard error streams instead of calling print.
As otherwise the standard out command line option won't operate properly, which makes testing problematic and means people using your command will have to write extra code to trap your output In the next chapter, I'll take you deeper into database management and show you how migration scripts work.
|
|
|
13:13 |
|
show
|
2:54 |
In the previous chapter I showed you how to write your own Django management command.
In this chapter, I'm going to circle back to the database and the ORM.
I'm gonna show you how Django handles when you make a change that affects the underlying mapping of a table.
You've seen the ORM models, each of which is abstracting away one or more table in the database.
You've also used the make migrations and migrate management commands.
But what do these actually do?
When you create or change a Django model, it is creating a corresponding migration file that specifies what is to be changed in the database.
The migrate command then invokes these changes.
So first I'll show you what these migration files look like, then go into more detail on how they get run and finally, I'll even show you how to back out of them.
If you need to, when you create a Django model, you are creating the proxy to one or more tables in the database.
That means when you change your model, those same changes have to be applied to the database.
Changes to the database may impact the data within the database.
So you have to be careful about how you manage the changes.
For example, when you add an attribute to a model that is adding a column in the database, what about data that's already in the table.
If your column isn't allowed to be empty, you're going to need to provide a default that fills it in for all the existing rows.
Making changes to an existing column, may require you to write scripts that explicitly deal with the change.
Say for example, you had a name column that you wanted to split into a first name and last name column.
You're going to need to add a column and deal with the data splitting.
Django doesn't do all this for you, but can provide hooks to help.
Likewise, removing a column with data in it can't really be undone.
You can put the column back, but unless you've explicitly stored the data from the column elsewhere, it's gone.
When you create a model or make a change to one, Django tracks this automatically for each change, a corresponding migration script gets created.
It is written in Python, but for the most part, it's just a data structure.
The scripts are created in a directory named migrations inside of each app.
These scripts should be managed like any other code in your project.
You do need to be a little careful here if you're working with multiple developers on different branches.
Let's say, you and I are both working on the same repo on our own branches.
If both of us touch the author model at the same time, a migration script will get created.
That will have the same change number in it.
Although the repo would detect if I stomped on your code changes during the merge, it won't detect this because each of us will get our own individual migration files.
So you need to make sure you properly coordinate model changes with your teammates.
Let's learn by doing.
|
|
show
|
1:50 |
To demonstrate how migration files work.
I've created a new app to store music in our library.
You know the deal here, management command to create the app and add it to settings.
This is the models.py file.
It's pretty basic, a model called song with one character field for the title.
With the model ready, all run make migrations.
Notice how that created the migrations directory here Let me open it up.
This is a Python module directory, so it has a dunder init file.
It also has the 0001 initial script.
This is created by make migrations and has data in it that dictates the structure of the table.
Let's take a look at it.
You can see a couple of different data structures here, all of which are inside of the migration class.
First, it has an attribute that indicates that this is the initial migration.
Seond is a list of dependencies.
If there was anything in here, these scripts would be run before this one in the migration process.
Third is a list of operations.
The operation here represents the creating of the table in the database.
The fields list has all the information the migration command needs to create a table that maps to the current version of the song model.
Although I only defined a title column.
Every Django model has a big integer primary key for its ID.
So there are two fields here.
Ok, let's migrate this.
|
|
show
|
0:55 |
And now I'll use DB Shell to take a look at it.
Dot tables is a sequel like command that lists all of the tables in the database The tables for models are named, starting with the app name, then an underscore than the model name.
So music _ song is the one I just created.
Dot schema is a sequel like command that shows the schema information about the table.
Here you can see all the sequel that was run to create this table and all the column info inside of it.
Okay, you've got the idea.
Let's make a change and see what happens.
|
|
show
|
1:23 |
That's my new field.
Let me make migrations again.
I spoke earlier about how changes to the table impact the existing data in the table.
Because artist's name wasn't specified to allow null.
The database has to put something in there when it creates the column for any existing data.
Now there's no data in this case, but it doesn't know that and it's trying to protect you.
I'm going to choose provide a one off default and provide the empty string.
If there had been data in the table, any rows that existed would get the empty string as their value for the new artist name column.
And the result of all this is a new file showed up in the migrations directory.
0002 song artist name.
The naming structure here is a number which indicates an order of running, then the model name and some detail about the change for a column, add, it shows the name of the column.
Let's take a look at it.
|
|
show
|
1:00 |
This time the dependencies field has something inside of it.
It states that the 0001 initial script in the music app needs to be run before this script is run.
That makes sense.
If the table hasn't been created, adding a field to it would be problematic.
The operations list here shows the change to the model.
Instead of creating a table, it is adding a column.
The add field object has all the info the migration script needs to do an alter table call.
Adding the column to the database including a default value of blank for any existing data.
Alright, let's migrate this sucker.
And by examining the schema, you can see that the change has been applied.
|
|
show
|
0:41 |
By using an additional parameter in the migration command, you can specify what migration level to go to.
Say you had 10 migration files but only wanted to go to the 5th one.
You could do that.
You can also go backwards.
Let's try that.
The current state had been 0002, asking to migrate to 0001 undoes the change.
See, back to the original schema.
|
|
show
|
0:30 |
Django stores the migration state of all the apps in a table called Django migrations.
Let's take a look at it.
There's a row in here for each migration applied for each app.
In our case, most of them are initial.
But if you look at the off app, for example, there are almost a dozen scripts that have been applied.
|
|
show
|
0:44 |
There's an easier way of showing all that stuff than digging around in the database.
The show migrations management command shows you app by app what migrations exist and the X indicates it has been applied.
If I scroll up a little bit, here's music 0001 has been applied.
But because I rolled it back, 0002 has not.
You can also see what sequel is related to a specific migration.
You don't even have to give the entire name just the number in most cases
|
|
show
|
1:25 |
Say you've been tinkering around for a while and you've added a lot of migration scripts.
There might be a big difference between your current state and production and all those scripts you've got in development and that will mean a lot would have to be run at your next deployment.
You can optimize this a bit by squishing migrations together.
You do that with the squash migrations command.
The argument here specifies the ending point for the squash and all the scripts up to it, get included in the squash.
Let me go back to the migrations directory.
The new file here is named after the start an end state of the squash.
Let's open it up.
The replaces attribute tells Django what was squashed.
Thus enabling the migrations command to know to run this script instead of the others.
This means you don't have to get rid of the old files, which gives you a nice clean way to back out of this squishing if you need to.
The result of squishing a create and a field add is just a single create.
but this create has both the title and artist name fields inside of it.
|
|
show
|
1:51 |
Look at that an entire chapter without any new code.
to test.
You can take the day off, you deserve it.
Let's review the chapter.
Django provides ways of tracking what changes you've made to your models and provides commands to help you keep the database in sync.
Migration scripts are created by the make migrations command and live in the migrations directory under each app.
The scripts themselves are Python and contain a class that specifies what is to be done to the database.
Although the scripts are created automatically, they are just Python.
The migrate command reads them in and builds the objects and uses those to do the migration.
This means you can modify them on your own and although not covered here.
There are even hooks for your own data migration management functions.
You can do other things to the database on the entry point or exit point of a script.
You saw how the migrate command can take a specific script as a parameter and thus moved any point in the migration history.
Both forwards and backwards.
Of course, if your script deleted a column, the data doesn't magically reappear, but the column will be restored.
In addition to all this, you learned some new management commands including show migrations and ask you all migrate that gives you detailed information about what Django is doing for you.
One last thing before I close up a tiny bit of history.
This migration stuff is a newer feature of Django and the functionality used to be part of a separate library called South.
So if you happen to be digging around in some old code and you see South migration scripts, those were the inspiration for what is now included in the library.
You're getting closer and closer to being able to manage a production Django instance.
In the next chapter, I'll talk about some considerations when it comes to deployment.
|
|
|
10:48 |
|
show
|
0:55 |
In the previous chapter I showed you what happens behind the scenes when you run the make migrations and migrate commands in this chapter.
I'll walk you through some of the things you need to consider when deploying your code.
Up until now you've been using the Django development server.
There's a clue in its name.
It's meant for development.
If you want to put something up for the public, you really shouldn't be using the DEV server, so you'll need to do a few things to get Django working in a hosted environment.
There's a standard for interfacing with web servers and Python scripts called WSGI that Django uses.
There's also an asynchronous version of this called AWSGI.
I'll give you a brief overview about how these wire your Django application to your web server.
And finally, there are some other things you need to be worried about, like managing your static files.
I'll walk you through that as well as some common deployment patterns.
|
|
show
|
0:53 |
I've said it a few times before, but it bears repeating.
The development server isn't hardened and is not meant for anything but development.
In production Django uses either the WSGI or a WSGI interface to connect to a web server.
WSGI stands for web server gateway interface and does what the name implies, acting as an interface between your Python code, Django in this case and the web server.
Think back a couple of chapters where I discussed the various types of content in your project.
There are Django views that spit out Web pages, static content like CSS, javascript, and images and files uploaded by your users, which Django calls media.
Each of these is handled a little differently and when you're deploying to a production server, you'll need to configure them appropriately.
|
|
show
|
2:00 |
Let's walk through what happens when you use the web to interact with a Django project.
First you type in a URL in your browser.
Your browser looks at the host name and ports part of the URL to figure out what server to talk to.
The web server answers the call and inspects the URL.
It looks at its configuration and maps that URL to a server.
In our case, this is the WSGI service which further maps to Django.
Django then examines the same URL and uses the project's URLs file to find a view.
That view gets called and results in the rendering of a template.
Returning some HTML.
The HTML is received by the browser and the browser's rendering begins.
Within that HTML there might be a reference to some static content.
The same process repeats going back to the server, this time with the URL of the site's CSS files.
Common configuration is for the web server to host the static content itself.
That means no need to talk to WSGI and Django.
Through the server's own configuration the URL is mapped to a directory and then the static files are served from there.
If that served file is a style sheet, then the content in it return to the browser, helps it further render the HTML by giving it style information.
Even later in this sample HTML is a reference to the user's avatar, wash rinse and repeat the URL goes up to the server.
These are media files uploaded by the user.
Typically these are still handled by the web server itself.
The server then sends the binary content of the image back to the browser so that it can be rendered, who knew so much was going on just to get a simple page going.
|
|
show
|
1:15 |
So what you've learned from this is in addition to getting your Django code somewhere, you're going to have to put the static files in a place where your web server can serve them.
There's a management command for that Django collect static.
It's part of the static files framework and when run it will copy all of the static files you've defined in your templates and directories into the appropriate production directory.
What does it mean to be defined as a static file?
The static files dirs setting lists all of the places in your project.
The command should look for files if you're using the static tag in your templates, it is pointing to these same places and where do all those files get copied to?
Well, the static route defines where that is.
As long as you're setting points to the hosting directory where your web server expects these things, you're good.
Well that and you need to make sure you've got permission to write there when you run the command.
If you want to do something fancier the static files framework is plausible.
Django comes with a file based one that you've used in your code but there are also third party modules that allow you to use S3 or similar services as a back end instead.
And of course if you really want to, it is yet another thing you could write yourself.
|
|
show
|
1:34 |
The simplest configuration for media files is for them to be served by the web server and with the right settings you just need to point it to a directory.
This mechanism means that anyone with the URL will be able to get at all the files though.
There are a couple ways of making this more restricted.
You can write a view that does permission controls and then instead of rendering templates, returns the binary file.
But then you're using Django to serve the media content instead of the web server.
It isn't really optimized for this.
There is an alternative but it depends on what web server you're using.
There's a specific way to write a view that returns an almost empty response with just a specific http header set.
The web server sees this header and intercepts it.
It doesn't get set down to the browser instead it uses the header to look for the appropriate file and serve it from a protected location.
You're still writing a view, but now all the view has to do is return a header.
The web server takes care of the rest.
Your view can have all the permission and validation code you need to protect the file without having to worry about serializing the media file to the user.
The header is server specific.
In Apache, it's called X-Sendfile and in nginx, it is X-Accel-Redirect.
Details about this are beyond the scope of this course but a little googling will get you there or you can do the best thing, depend on someone else's code.
There are third party libraries that handle all of this one of which is called Django Protected Files.
|
|
show
|
1:36 |
By now, you're intimately familiar with your projects settings.py file.
That's where all the configuration lives.
Some of the content of that file really shouldn't be in your code repo though, especially the secret key.
You may also want to have different settings for different environments.
If you've got a test server as well as a production server, they may have different needs.
For example, debug should be false in production but you might want it to be true in test.
There are a couple of ways of dealing with this situation.
Some production servers will pass environment variables into your program.
You can use these to change certain settings.
One thing to remember is that your settings.py is just a Python script.
It gets run by the Django framework.
Most of this running is just defining variables that then get imported into the underlying configuration but it is running this means you can put code inside of it.
You know, those environment variables I just mentioned well you could read them in and change the content of your settings.py file automatically.
The trick I usually use is to do an import at the bottom of settings.py that imports all of the content of another file.
Often named local dash settings.py.
You don't commit local settings to your repo because you'll want a different one for each environment.
One way of handling this is to have a test settings and a prod settings and then when you deploy, copy them to local settings.
There are also 3rd party libraries that address this kind of configuration.
I've never personally used any of them so can't speak to their value but they are out there if you want to explore.
|
|
show
|
1:15 |
Exactly what configuration you need to do will depend on where and how you are hosting.
My personal favorite provider is called opal stack.
I'm not associated with them in any way.
I just do host some of my content there and I'm happy with their service.
From them you can get a small virtual or dedicated machine for your needs and they give you ssh access to the instance along with a web based dashboard to do some conflict.
It provides a hosted postgres if you wish to use their database and all of it is fronted by NginX.
It has a concept of pre rolled applications that you can use to quickly configure.
One of them is Django where you can put all your project files.
Another one is for serving static files where you can point your collect static command.
And of course to do the media, you can just use the same kind of service as the static one but put it in a different directory and change the permissions appropriately.
Inside of the opal stack dashboard you wire up some URLs typically pointing slash static to the static app slash media to the media app and slash to the Django app.
Anything not under slash static or slash media will get sent to your project.
And you're set up and set ot go.
|
|
show
|
0:49 |
A very popular hosting service for Python and Django is Heroku.
Never used it myself, but people whose opinion I respect are happy with it.
It's a platform as a service offering, which means they've done a lot of the configuration stuff for you.
They also have hosted PostgeSQL and all of it is built on top of AWS, which means it scales like nobody's business.
There's a command line tool that you can install for remotely managing your instance and all your configuration is done through a git repo.
With the right keys in place you push content and when Heruko sees your change, it updates the server through a pole.
It also has environment variable management, which is the perfect place for your secret keys.
It even is Django aware, so it'll run collect static for you when you do a push.
|
|
show
|
0:31 |
This chapter has just been a quick overview, deployment is hosting provider specific, so it's hard to give general advice.
A key takeaway, though, is that your DEV and production are different beasts and you need to separate out the Django views, the static files, and your users media files.
You should be careful with secret keys and never commit them to a repo, let alone a public one.
And there's a lot of choice out there for hosting.
Your provider will have details about how to configure for Django.
|
|
|
20:28 |
|
show
|
1:59 |
In the previous chapter, I covered some of the things you need to think about when deploying a Django application into a production environment.
In this chapter, I'm going to briefly introduce you to a couple of the more popular third party libraries often used when coding with Django.
You've seen along the way, just how extensible the Django framework is.
The core developers have been very careful about building Django in applicable fashion, letting you the programmer take advantage of the same structures that underlie Django itself.
Everywhere where you've been able to write your own thing is a place where you could also create a module that others could plug into their code.
How to do this is beyond the scope of this course.
But what this chapter does talk about is several libraries where others have written such modules that you can take advantage of.
The first one I'll show you is one of my favorites.
Grappelli, it's a drop in replacement for the Django admin, rewriting all the templates, giving it a makeover.
As you've been coding along, did you wish you had more info when you made a mistake and the debug screen came up?
Well, the debug toolbar might be what you're looking for.
It is a plug in that exposes a whole load of info that might help you find flaws in your code.
Single page applications are quite common nowadays, those are applications where the user interface is purely on the client side and created by javascript.
Libraries like React, Angular, and View.JS all use this methodology.
That doesn't mean you still can't use Django on the back end.
All that JSON and server side business logic has to live somewhere.
To do this your views become simpler and essentially become data APIs.
To help with building this kind of interface, you might use the Django Rest Framework (DRF) or DRF to its friends.
It makes wiring your data models into a serializable API form just a few lines of code away.
This chapter will cover these three packages in a bit more detail, but will also wrap up with some pointers to other commonly used packages that you can investigate for yourself.
|
|
show
|
0:40 |
Most third party libraries are encapsulated versions of a Django app, no different than an app you've written for yourself.
As such they're typically installed through pip install and then by adding the apps name to the installed apps configuration in settings.py.
Like I said, just like the apps you've been writing.
If you want to see if the thing you need has been written by someone else already.
Two good places to look for Django specific packages are awesomedjango.org and djangopackages.org.
Django packages even has a nice little comparison grid for different packages that are solving the same problem.
This can be helpful when you're picking from several choices.
|
|
show
|
1:57 |
The first package that I'm going to demo for you is Grappelli.
It's a restyling of the Django admin, replacing all the admin templates with new ones that are slicker and more modern.
The docks for Grappelli are at django-grappelli.readthedocs.io.
Let's open up PyCharm and install Grappelli.
The first step is to install the package using pip install.
In case you're curious about the name.
The Django package is named after the jazz musician Django Reinhardt.
So when it came to building a new gooey for the admin, the Grappelli packagers thought it only appropriate to name it after Djangos musical partner, Stephane Grappelli.
Grappelli is a Django app that has overloaded the templates used by the Django admin.
As it is an app, you need to tell Django it's there, you do this by adding it to installed apps like any other app.
It's very important that this one goes before the Django contract admin app.
Usually it doesn't really matter what order you have here.
But in this case Grappelli needs to take precedence over the admin.
Grappelli also has some views of its own.
You're going to need to register its URLs.
The next step is to include a path.
Let me open up alexandria URLs.py.
And like with the install apps, this declaration needs to go before the admin path as well.
Grapplli's interface includes some images and things that are included as static files.
If you were installing this library in a production server, you'd also have to run the collect static command.
But you're probably going to have done that anyhow for your own stuff.
All right, all that configuration is done.
Let's go take a look at the result.
|
|
show
|
1:30 |
As usual, I'm running the DEV server in the background here.
Let me just go to the admin page and right off you see a difference.
Some cleaner styling for the login page and a lot brighter because I'm not in dark mode.
This is the main screen.
Let me go take a look at some of the books.
Obviously style is a personal preference thing, but the lines, an organization of this just feel cleaner to me.
Granted the addition of dark mode to Django has made a difference.
Funny how something as simple as coloring can make it feel richer.
The action bar is down at the bottom, consistently across the pages instead of squished into the title.
And the filters are now a drop down which gets them out of the way and gives you more real estate for your data table.
Password management and log out links have been moved under a dropdown with the user's name.
Again, meaning less clutter.
Let me click one of the books.
Again this just feels cleaner actions are all along the bottom in a footer, which makes them seem consistent.
There's a nice breadcrumb piece at the top here which is in the regular interface but it's kind of hidden and you don't really see it where it's nice and clear here.
All a matter of opinion, but I like this one.
Not much else to show off here.
If like me, you prefer the look, it's a quick install and it's ready to go.
|
|
show
|
2:30 |
The Django rest framework is well, a framework for building rest interfaces.
If that's new to you rest is a loosely defined protocol for communicating with a server using http actions.
The URL is used to uniquely identify a piece of data or a query while the http method indicates what you would want to do with it.
For example, a get with the URL for a person's ID will fetch data about that person.
A post for the same well would allow you to overwrite their data with new information.
As the data in question is almost always something with your models, the DRF gives you a way to wrap your existing models, declaring a restful interface with very little code.
The body of a rest request, also known as the payload can be in many different formats.
Out of the box the DRF supports, JSON, YAML, XML, variations on JSON, XLSX, LaTex in case you're an academic and several of the export methods that pandas supports, like CSV, another kind of Excel, and even PNG files.
On top of all this, the DRF also has a browsing interface for your API.
While you're debugging you can use the browser to look at what payloads are coming back and manipulate your data.
More information on the DRF can be found at django-rest-framework.org So what exactly does the DRF provide for you?
Well, it handles serialization of your models to and from a payload and it deals with the different kinds of http methods used in rest.
It gives you fine grain control over what's in your API by providing a series of classes you can use to declare a view in the API which are kind of like the analog of the model form.
You specify the model you're wrapping and the class takes care of each of the http methods.
You can override any of the methods if you need to do something specialized in your interface.
Of course as you're exposing data, you probably want controls on it.
So the DRF has a robust authentication and authorization mechanism.
You can also define the relationships between data models, a rest query might ask for books and return a payload that include info on the author embedded inside the result.
Because all of this is done over URLs.
The DRF gives you the ability to control what URLs are used for what data and where all this goodness gets mounted.
Let's go play with some code to see what it's like.
|
|
show
|
4:33 |
The RFIs just an app.
A fancy powerful app but an app nonetheless.
And of course it needs to be registered.
So I'll open settings.py.
Note that the name of the app is different than the name of the package rest_framework is the name of the actual module because I want to use the browsing interface, I'm going to need to register the URLs that are used for authentication.
Let me open up alexandria URLs.py and this is the line that registers the packages off URLs I'm going to demo the DRF by writing an API for our author data.
I need two things for this.
Something that serialize is the author objects and a view for the API.
I'll start by creating the serializer inside of catalog serializer.py I need to import DRS serializer module along with the author object that I'm going to serialize and then reminiscent of model forms, I create a serializer class based on the author model.
See what I mean?
By being like model forms, you specify the model that this serializer is associated with along with the fields that you want serialized and you're good to go.
With the serializer in place, next up is a view for the API.
I'm going to put the view with the other views for catalog.
Let me open up views.py, there are two imports that I need.
The API view decorator indicates that this is going to be an API view and the response object is the DRF equivalent of an http response object that you would use in a view.
And this is the serializer I just finished building, now to add the view.
Let me scroll down to the bottom here.
Structurally, this is similar to the views you've written before with just a few small differences.
First the decorator indicates that this is an API view and the get parameter says it's only going to support the http get method.
If you were going to do a full interface, there are class based views that take less code than writing this for each of the http methods.
But this is a quick way if all you're providing is a read only interface.
On line 63, I query all the authors and then I create a serializer object based on the one that I declared before it takes the query set result of authors and an argument that indicates that there are multiple things being serialized here.
I then get the output by using the serializer objects, data attribute and wrap it in a context dictionary.
All of this gets put inside a response object and the view is ready to go The last thing to do is register this URL.
Let me open up catalog URLs.py, if you're going to do a lot with the DRF it has a full route management mechanism that helps you manage many different endpoints with very little code.
But for this quick demo, I'm just registering this view like any other.
Let's go take a look at this in the browser.
|
|
show
|
1:01 |
This is the browser interface that the DRF provides you.
It automatically detects that this is coming from a web browser in view mode.
Rather than say, from a javascript call and gives you this browser interface.
The payload here shows you what would be returned if this was a javascript call.
In this case a JSON representation of the authors in our database.
I only wrote the get interface, but if you were doing a full rest API, the menu in the corner here would allow you to do posts, puts and deletes to manage your interface.
I've really only scratched the surface here.
The DRF is a powerful tool that allows you to quickly put together a rest API with very little extra code on top of what you would normally do for your Django project.
If you find yourself writing a single page application, the DRF is a library you definitely want to look into.
|
|
show
|
3:30 |
The next app is the Django debug toolbar.
As the name implies, it is a toolbar that overlays your Django views, giving you access to all sorts of info, for example, settings, headers, request information, the SQL queries that were run, static files, what templates were used and how they were inherited, how the cache is behaving, and any signals that got fired or tracked.
And in the spirit of Django it's applicable interface.
So there are even third party libraries built on top of this third party library to give you even more info.
Let's go see the debug toolbar in practice.
You can probably guess the next few steps.
With the app installed, there's a couple more pieces of configuration.
you need to be worried about.
The setup I've been using up till now already has them done but the tool bar won't work without them, so you need to make sure they're there.
First you have to be using Djangos static files app so it needs to be installed in installed apps.
But like I said, you're probably doing that anyways.
Second, the template's actors value needs to be set to true.
This tells Django that an app can have templates inside of it.
And as the toolbar uses this, you need to have this on.
And finally, because it uses static files, you have to have a static URL defined.
All these values are the defaults but if you've been messing around, you need to make sure these are set correctly in order for the toolbar to work.
There's one more piece of configuration you need to do.
Middleware is a pass through mechanism that all requests go through.
This allows you to write plugable code that impacts all the views in a project.
The debug toolbar does its magic by examining your views through this mechanism.
Note that the order of placement here is important.
This should go as early as possible in the list but has to go after any encoding middleware so if you're compressing your views with zip, this has to come after that.
In my case, I don't have any of that going on so I just put it at the top.
For safety reason, the toolbar will only work against specific IP addresses.
This helps prevent you from accidentally enabling the toolbar in production.
I need to add a little more configuration to tell the toolbar where it can be used.
Setting internal IPs tells the toolbar it will only work for the local machine which is about as locked down as you can be.
As the toolbar has its own views.
I need to add those views to my URL registry.
Let me open up alexandria URLs.py and there you go it's demo time.
|
|
show
|
2:48 |
You can see the toolbar has been installed as it shows up as a semi transparent overlay in the corner here.
Let me click it open.
That's a long list, isn't it?
Note the check boxes here for performance reasons.
You might want to turn some of these off which you can do by clicking one of these boxes.
As the homepage doesn't have much on it, let me click through to the author's list.
Now, let's go through some of the debug info.
Each of the categories in the toolbar are clickable, clicking it opens a details panel.
I'll start with history.
This shows all the places I've been along with the request variables and response code.
I can close a panel with the X in the top corner.
If I click time, I see all sorts of information about how long the page took, including how many context switches and a graph of where the page spend its time.
Settings shows all the configuration in setting.py as well as the other bits of configuration Django is added after it loaded your conflict file.
Headers shows all the http headers in both the request and the response as well as the underlying environment for WSGI.
The request panel shows details about the request, including what arguments there were, none in this case and what cookies were set.
SQL shows the queries run for this page.
In a startling amount of detail including the amount of time it took to run the query.
If your pages are running slow.
This is a quick way to see whether it's the page itself or the query underneath that's causing the problem.
Static files shows info about what files are fed from static It is even differentiating those files that are directly in the template versus those that are inherited into the template.
And the last one I'll show you is the templates panel.
This gives you info on the template being rendered what it inherits from and what context processors contributed to it.
If I click the CSRF context processor, you can even see what object got pulled in by it.
Are magic CSRF token.
The debug toolbar is invaluable when you're hunting down problems and the documentation has information in it.
On 18 more installable panels that could help you squish your next bug.
|
|
|
15:56 |
|
show
|
0:24 |
In the previous chapter, I highlighted some popular third party apps that can lighten your coding load.
This final chapter summarizes everything you've learned.
If you've made it this far well, congratulations.
You've gone through a lot.
You started many hours ago with how to create a Django project and journeyed all the way to taking advantage of the thousands of third party apps that are out there.
Let's look at what you've covered.
|
|
show
|
2:47 |
Way back in the beginning, you took the first steps towards a new Django project.
You installed Django and then used the Django admin command line tool to start a new project.
A Django project is comprised of the somewhat confusingly named Django apps.
So the next step after creating a project is to create an app.
Django is a web framework after all.
So of course it comes with a web server.
The run server management command runs a development server on port 8000, which you can use to test your code.
Once you've used the start project and start app commands, you'll end up with a directory structure that looks something like this.
The structure has a bit of a weird doubling in it.
Whatever you named, your project will show up as your project root and then there will be a system configuration directory named the same thing underneath it.
Here, you see the alexandria in yellow as the project route and the one in light blue as the configuration directory.
The configuration directory has files for configuring and managing your Django project.
The WSGI and a WSGI files are used to wire your Django code to a production web server.
The settings.py file contains a number of variable declarations that are used to configure your Django project, including the all important installed apps that lists all the apps you wish to use.
The URLs.py file maps URLs to views, the code that generates a web page.
This file can also reference and include other URL mapping files so you can create a hierarchy of mapping is to keep your code nice and organized.
By default, Django uses SQLite as its database engine.
So once you've started creating models, you're going to see the db.sqlite3 file.
You can change what database engine to use in settings.py.
Django provides a number of command line management tools for configuring and managing your project.
You run these through the manage.py script.
This is a wrapper to the same code you used to run the start project and start app commands.
You structure your code in a Django project with one or more apps which really should have been called modules.
When you use the start app command to create an app, Django creates a cookie cutter set of files for you.
Each app has an admin.py file that controls how the app interacts with the Django admin web tool.
An apps.py file that contains meta information about the app.
Models.py file that specifies data based model objects.
The tests.py file for app unit tests.
A views.py file that contains views for generating web pages and a migrations directory that contains scripts that help Django manage changes you make to your object models.
|
|
show
|
1:28 |
A user types of URL into a browser which ultimately gets sent to Django Django then needs to map that URL into a function call or class responsible for generating the web page for that URL.
The system URLs.py file contains the master mapping of URLs to views.
It contains a variable named URL patterns, which is a list of path objects that map URLs.
There are several different kinds of things you can do when mapping a URL.
You can use a built in view like the redirect view to serve a file directly like a favion.
You can also map a path to another URLs file either through importing that module or using the include function to specify the name of it in text.
In the first line of the light blue color here, I've mapped a URL directly to a view.
The say hello URL will get mapped to the say hello method inside of the primer.views module.
If you want to map the base URL.
You map the empty string like in the third line of the blue code.
By default Django development server won't serve uploaded files which it calls media files.
You can get around this by using the static function from the static files module to mount the directory that contains your media files and serve them.
As this is a bad idea in production, I've added this mapping only in the situation where the settings configuration debug is set to true.
|
|
show
|
1:26 |
A view is a function or class responsible for returning HTML and is typically mapped to a URL.
Here, you see the book view which is implemented as a function.
It takes two arguments, the request, and a book ID.
All function views take at least one argument the request.
The request contains information about the http request that resulted in the view being called.
If your view takes other arguments, the URL mapping mechanism can be used to map parts of the URL to the argument in the function.
As well as doing typecasting so that your numbers can be actual Python numbers instead of a string containing a number.
There are a number of shortcut utility functions you can use to reduce the amount of code needed to write a view.
The get object or 404 shortcut does a look up of a model object.
It takes at least two arguments.
The first is the name of the model being looked up and the second is any arguments in the filter that determines the object.
If the query results in a single object it is returned.
Otherwise, this shortcut raises a 404 exception telling the user they tried to do something they shouldn't have.
Most views use templates to generate the HTML that is to be sent back to the browser.
As this is such a common pattern the render shortcut takes the name of a template and some context data and renders a result, wrapping it in the needed http response object to be returned by the view.
|
|
show
|
0:59 |
HTML can be quite repetitive across a website.
You've got headers and footers that often appear on every page and stylistic pieces that might be reused.
To help you write less HTML, Django provides a templating mechanism that allows you to compose and reuse HTML like you would with object oriented code, Django's template engine uses tags to denote different things that need to be done when rendering a template.
The extends tag works like inheritance and will insert the content between the block tags into a parent template.
There are also tags for doing conditional rendering, like in Python, there are four and if then else mechanisms allowing you to conditionally or repeatedly include a chunk of HTML.
To avoid hard coding URLs into your HTML the URL tag does a look up for a named URL.
URLs can be named when they are registered in a mapping file.
This means if you change your mind about a URL, you don't have to go hunting for it in all of your HTML.
|
|
show
|
1:48 |
Django comes with an ORM.
That's object relational mapping, an abstraction layer that sits on top of your database.
It allows you to think about a group of objects rather than the underlying tables the information is stored in.
You create a data model object by inheriting from Djangos db.models.model class and declare fields which get mapped to columns in the database.
Django provides dozens of different field types and allows you to create foreign keys and many to many relationships between your models.
Some of the fields will even auto generate their content like a date time stamp.
You can further control how your data objects behave through the use of an inner class called meta.
Within this class, you can configure things like the default order of query results as well as how Django will refer to objects of this type inside of the admin web tool.
Speaking of the admin web tool, you can overload the dunder string method to make the objects appearance in the tool be whatever makes the most sense for your project.
A lot of the work you do in a web application is simply CRUD, that's create, read, update and delete the four operations you need on your data objects.
To help you manage these operations, Django comes with an admin web tool that acts as a gooey interface to your ORM.
With very little extra code, you can create, view, edit and delete all of your data objects and their relations You register a data object with the admin tool by declaring a model admin object in admin.py.
These classes also provide ways of changing the display of the various screens in the admin that relate to this object, thus allowing you to customize what fields are shown in the object listing and provide links to other objects in the interface.
|
|
show
|
1:01 |
The web isn't much fun without people using it, but that can mean a whole lot of extra work.
Managing user accounts means registration and passwords, which means a bunch of extra screens.
Django provides utilities for helping manage your users, including controlling authentication, getting users to prove who they are as well as authorization, specifying what they can do.
One of the ways of controlling who can do what is the login required decorator.
If you wrap a view with this, it will prevent anonymous users from seeing the view.
Django provides methods for managing registration and password management by giving you some pre made views.
As you're likely to want your site to look like, well, your site.
Django doesn't give you templates for this.
You have to write your own.
The views provided, manage login and log out, password changes, and password resets.
The password reset mechanism ties into the email system and will send your forgetful user a one time link so they can reset their password.
|
|
show
|
1:57 |
HTML uses the form tag to give users a way of sending information to the server.
Django provides a series of tools to help generate and manage these forms.
Oftentimes what the user is doing is interacting with one of your data objects.
For example submitting a book review.
This is so common that Django gives you a class that can turn a model object into a form with just a few lines of code.
This form object can then be rendered in your templates, saving you from writing a lot of extra HTML.
To interact with the user via a form.
You use two different http methods get and post.
You first use get to render the page with the four minute, which usually starts out empty.
And then when the user submits the form, the same view can detect that the http method was post and process the form.
It first needs to validate the form and if the form was valid you do whatever you need to do, like save some data, then redirect the user to another page.
If the form was invalid, then you can re render the same form augmented with error data, telling the user what they need to fix.
HTML forms, not only provide a way of sending text data but properly configured can also allow the user to send a file.
This is done through the inclusion of binary data in the http post method.
Django calls these uploaded files, media and handles them specially.
You typically write anything uploaded to disk.
The file name on the disk doesn't have to be the same as what the user named it.
In fact, it's best practice to not name it the same to prevent users from accidentally overwriting each other's files or doing even more malicious things.
By default, all media files are public.
If the user knows the URL, they can get the file.
There are ways around this, but it requires extra code.
Django isn't optimized to send files.
So in production mode, you really should have your web server be responsible for the media files.
|
|
show
|
0:33 |
Out of the box, Django provides a series of management commands that help you create your project, manage your files, manage your database, and much more.
As with a lot of things in Django, this same power is available to you as a coder.
You can write your own management commands by creating a management commands directory in your app and writing a script that contains a class that inherits from base command.
The handle method in this class is what is called when your command is invoked from the terminal, and the command uses arg parse to manage switches and options sent to sent to you.
|
|
show
|
1:24 |
The ORM is an abstraction of the database using data models.
Each model is a proxy to a row in the database when you need to change your model, that means a corresponding change needs to happen in the database.
Django includes tools for managing these schema changes through a mechanism called a migration script.
A migration script is a Python script that indicates what changes need to happen in the database and it's used by the migrate command to execute those changes, migrations can happen either forwards or backwards with a few caveats.
Although this is a powerful tool, it isn't magic.
So you need to be careful with how new columns affect existing data and how removing columns will delete data permanently.
The setting.py file is the heart of configuration for your project.
Unfortunately, there's only one of these which means your desire to have different settings in dev testing and production requires a bit of extra work.
When you go into production, your web server will talk to Django through either the WSGI or AWSGI interface and you should configure the server so that it is responsible for static and media files.
Leaving Django to do dynamic web pages as it was designed for.
Don't forget that by default, your settings.py file has a secret key in it and could have even more sensitive stuff depending on what libraries you're using.
Be careful not to include these in a public repo.
|
|
show
|
0:49 |
As a final bit of icing on the Django course cake, I walked you through some popular third party libraries that can make your life easier.
This is possible because Django was designed to be pluggable from the get go.
And many of these libraries are really just Django apps.
The libraries I covered were Grappelli to make your admin prettier, Django debug toolbar to help you better understand what is happening and squish those pesky bugs.
The Django rest framework for mapping your data objects to a rest API.
Django allauth for managing authentications, including through social media accounts, pytest Django in case you prefer to test the pytest way.
Django extensions, which is a tool chest filled with lots of helpful items and finally, Django import export.
That provides tools for importing and exporting data to Django land.
|
|
show
|
1:08 |
If you'd like to learn a bit more Django, why not check out the HTML plus Django course.
HTML is a relatively new JavaScript library that lets you write dynamic web pages with little to no JavaScript.
When you click a link in a web page, the browser fetches new content replacing the page.
HTML does the same thing, but with parts of a page, allowing you to fetch snippets of HTML and dynamically insert them into your content.
It does this by adding new attributes to your HTML tags.
By working this way, you can modify a page using the same views and tools you use with Django today.
The Talk Python course builds on top of a Django-based web project, with each lesson adding a new dynamic feature to the site.
You'll learn how to do click-to-edit by replacing a link with a form, search as you type, dynamically populating search results on a page, infinite scroll, and more.
And since you've already taken the Django course, you've got the needed prerequisites.
Learn about HTMX and take your Django coding to the next level today.
|
|
show
|
0:12 |
Well, it's been a bit of a marathon.
You've covered a lot of material.
I hope you found the course useful.
My name is Christopher Trudeau, feel free to reach out to me on twitter.
|