Wagtail for Beginners | Kalob Taulien | Skillshare
Play Speed
  • 0.5x
  • 1x (Normal)
  • 1.25x
  • 1.5x
  • 2x
45 Lessons (8h 7m)
    • 1. Introduction to Wagtail for Beginners

      1:37
    • 2. What is Wagtail CMS?

      2:09
    • 3. How to Install Wagtail

      10:49
    • 4. Getting Started

      3:26
    • 5. Logging into the Admin

      2:17
    • 6. Wagtail Walkthrough

      10:28
    • 7. Editing the Home Page

      23:20
    • 8. Working with the Base Template

      7:06
    • 9. Services App and Pages

      20:19
    • 10. Service Page

      11:42
    • 11. Service Listing Page

      14:19
    • 12. Adding Page Validation

      7:36
    • 13. Adding a Header and Footer

      7:32
    • 14. Adding Debug Tools

      15:47
    • 15. Flex/Misc Page

      6:37
    • 16. Testimonial Objects

      16:13
    • 17. Welcome to StreamFields

      5:52
    • 18. Creating the StreamField App

      3:05
    • 19. Your First StreamField

      13:48
    • 20. Repeating StreamFields

      12:55
    • 21. Simplifying Repeating StreamFields

      18:29
    • 22. Custom StreamField Logic

      9:12
    • 23. Image and Text Block

      12:50
    • 24. Radio Block

      4:30
    • 25. Call to Action Block

      5:37
    • 26. Testimonials and Snippets

      9:51
    • 27. Table StreamField

      11:21
    • 28. RichText StreamField

      15:27
    • 29. Image Block StreamField

      6:18
    • 30. Custom StreamField Validation

      9:53
    • 31. Limiting Pages

      11:44
    • 32. Navigation Menus

      17:47
    • 33. Navigation Menu Templates

      16:09
    • 34. Contact Forms Part 1

      19:38
    • 35. Contact Forms Part 2

      9:57
    • 36. Limiting Contact Form Choices

      4:55
    • 37. .webp Image Support

      2:49
    • 38. Other Ways to Learn Wagtail

      10:47
    • 39. Global Site Settings

      14:29
    • 40. Changing the Wagtail Admin Logo

      2:38
    • 41. Adding Caching

      15:14
    • 42. Caching the Navigation and Footer

      12:08
    • 43. Adding a Sitemap

      2:37
    • 44. Prepping for Launch

      7:08
    • 45. Website Launch!

      38:33

About This Class

Welcome to Wagtail for Beginners!

This course is designed for developers who are new to Wagtail CMS. Together, we'll create a brand new Wagtail website from scratch, and at the end we'll deploy it to the web. Here's a preview of the final website: Rocketman

Note: This course assumes you have a basic understanding of Python. If you know some Django, that's great, but not required.

If you're unsure whether this course is for you or not, consider the follow topics we'll be covering together, and if you're unfamiliar or not completely comfortable with all of these subjects then it's likely this course is for you. Here's what we'll be learning:

  • Installation
    • You'll learn how to install Wagtail using venv and Pipenv
  • Create 7 custom
    • Including services, misc pages, testimonials, StreamFields, a custom menu, contact pages, and global site settings
  • Contact pages
    • Creating the initial page and the landing page
    • Customizing contact form options
  • StreamFields
    • Basic StreamFields
    • Repeating StreamFields
    • Custom StreamField validation
    • Table blocks
    • Reusing StreamField components
    • Adding additional logic to StreamFields
    • 2 ways to create StreamFields
  • Custom pages
    • Page validation
    • Custom page fields
    • Working with images and foreign keys to other models
  • Create listing pages to automatically populate content as soon as you create a new page
  • Caching
    • How to speed up your website with caching
    • How to delete caching when you save a page or model
  • Create a custom menu
    • You'll create a sortable menu system from scratch
    • You'll be introduced to Orderables and Clusterable Models
    • Register the menu with Wagtails ModelAdmin
    • Create a custom Django template tag to work with your menu templates
  • Create Django models
    • Use Wagtail as the main admin interface
    • How to turn Django models into reusable pieces of data (Wagtail snippets)
  • Create custom site settings
    • You'll make site settings globally accessible
    • Cache these settings
    • And delete cache when the values are updated
  • Custom image cropping and renditions
    • And .webp image support
  • Creating a sitemap
  • 2 super helpful debugging tools
  • My personal methods for learning Wagtail quickly and efficiently
  • Launch your website on a brand new server
    • Using Digital Ocean and Ubuntu 18 with Nginx and Gunicorn
  • Install an error monitoring service for your live website

And here are some bonuses this course comes with:

  • All the source code
  • The frontend build tools to customize the theme
  • A sample nginx config
  • Sample gunicorn files

If you think you could benefit from a course like this, definitely enroll today and let's get started.

(Preview of the website we'll be creating)

b6181671

Transcripts

1. Introduction to Wagtail for Beginners: Hello and welcome to wag Tail for beginners. My name is Caleb telling I'm going to be your instructor. I'll be the voice behind the video. I'm also a member of the Corps Wag Tail Team. And in this course, we're going to be creating a full wag Tail website from scratch. Now, this course may or may not be for you. We will be covering a lot of different things in this course, but if you're already an advanced wag tail developer, this course probably won't be for you. But here are some of the things that we are going to be learning. In this course, we'll learn how to install Wag Tail with two different virtual environments will create different wag tail pages, will manage templates will add data to template context. So if you don't know what that means, of course is definitely for you. We're going to add and create manage several different stream field types will create a custom jangle model and then allow wag tail to modify it. Use it, enable it reuse the data all over the place. In a thing called a snippet, we'll create listing and detail pages will even create a contact form that can email. Then we'll get into things like global site settings, managing page, hierarchy page and stream field validation. And then I'll tell you some tips and tricks on ways you can learn Wag Tail faster and easier and in a more playful matter will work with images and different image renditions. And then we'll talk about speeding up your website with a very simple form of cashing. Once we're done all that, we're going to deploy this website to the Internet. So if you think you would benefit from a course like this, definitely step inside and we can get started right away. 2. What is Wagtail CMS?: What is wayto rectal is pythons number one most popular content management system. Chances are you've heard of WordPress. Wait Till is like WordPress. If WordPress was professionally developed in this decade, Wait tells extremely developer friendly, and it piggybacks on the Django framework, which is a python framework. And that means wait till is capable of doing everything that jango can. It can also do everything that python can dio. Now, if you're looking to create and launch a website in the next 10 minutes like a WORDPRESS website, this is not the content management system for you. Waiter was built to help people manage their websites properly. That typically means there's ah, continentally person, a designer, a developer, maybe a few other people with some sort of investment in the website. But generally, the three base ingredients for an amazing website is a Web developer, a Web designer and some contents entry person, or someone who deals with a project like a project manager that could even just be your client. The nice thing about Wag Tail is wait till was built with all of this in mind. Now, Wag Tails, a content management system is actually quite performance. It's fast is what I'm saying. It comes with lots of different features that are enabled by default or if they're not enabled by default. They're super easy to enable. But best of all, wake toes 100% open source. And there is a great developer community for any type of support that you need. That being said, if you were considering using, wait till consider this wag tail is fast. It's built off of a very large, secure and proven framework called Django, and all of that is built on top of python. Python runs everything from websites to artificial intelligence to machine learning to simple programs that you might use every day and advanced programs for enterprises that you might not use everyday. You might only use at large, large companies. The python literally does everything, and wag tail is pythons Number one most popular content management system 3. How to Install Wagtail: before we can use wayto wait till needs to be installed, and before that you need to have python installed. So first things first. If you don't already have Python installed, head on over to python dot org's click on Downloads and download the version of Python that you want. I'm going to be using Python 3.7. Any version from Fight on 3.6 or greater is recommended, so choose Python 3.63 point seven or 3.8. Download it, install it. And once you have python running on your computer, you should be able to just open up your command line. Program your favorite command line program and just type Python Dash V, and you will see the version of Python. So I'm using Python 3.7 point two. Once again, you're going to want to use Python 3.6 or newer now to create a new python project. We should put this inside of a virtual environment. Basically, this is a layer between your python project and your computer. We use virtual environments pretty liberally so you can have different package versions in different projects without code conflicting on itself. or you can have one project running Wag Tail 2.4 in another project running. Wait till 2.7 and they will never conflict. Just because you have a different version doesn't mean they should run into problems, and that's what a virtual environment does. It allows us to separate that problem. A virtual environment can actually even go one layer deeper. You can create a virtual environment using a different version of Python. We're going to be using standard virtual environments. So if you're using Python 3.7, your virtual environment is probably going to be python 3.7 as well. But if we wanted to, we could say, 01 project has to use Python 3.4, and another project has used Python 3.7. And maybe we're on another project. And heaven forbid, it has to use Python to which, if you're using python to please upgrade to Python three Python to is definitely deprecate ID. Now let's go over to ways to start a virtual environment for a wag tail site. The 1st 1 is with a thing called a Venn VVe envy, and I believe this comes with newer versions of Python, so you shouldn't have to install anything. This is actually one of the easier ways to install a virtual environment. And the second way is using a method called Pippen. And this one is actually easier. But it does require you to actually have pip and installed. And before we do anything, you're going to want to make sure you have pip installed. Pip is pythons Package manager. Who? You, pip Dash V. You can see I'm using Pip 18.1. This is my location of where Pip is being used. And it's running Pip on python 3.7. So that's good. You should be able to write Run, Pip, Dash V and also get some sort of output like this. All right, we're going to install Wag Tail using ven ve envy. So I already have python running and you can see him running Python 3.7 point two. So I'm gonna do Python Dash M event and then the name of whatever virtual environment I want to call. I'm gonna call this Rocket Man Ven. Actually, what I'm going to do is create a new directory first. So let's cancel that Lets do em que dire Rocket Man CD rocket man And you can see my working directory here. So I'm in users Kale, Italian websites, Rocket Man. And now I'm going to do python dash m ven rocketman vend. The name is not really important. It's just your virtual environment. So I'm gonna call it Rocket Man Ven and we're going to see is going to create a new directory for me here. So let's do an ls Dash l. A. And we can see that there's a full during here called Rocketman venture right in there to get into this thing. All we have to do is type source rocketman, Venn been activated, which, if this is your first time, that's gonna look really weird. But, you know, after a little while you get used to it. And I am now inside of this virtual environment and you can see that because it says rocketman ven in brackets to the left of my user. So now at this point, I can do Pip V. We can see I'm using Pip 18.1. It's coming from my virtual environment. That's the folder here. That's the directory Python 3.7 so that all looks good. Let's go ahead. Install wag Tail now. So, Pip install. Wait, Tell. All right, so wait has been installed. I can now do Pip show Wag Tail. It shows me that I'm using Wag Tail version 2.7 Right there. I can also do pip freeze to see all of my packages that are installed in this virtual environments. I've got beautiful soup. Certify Django Django Tree beard, html five, lib pillow requests six your l'Abbe, wag Tail willow. All sorts of stuff. So that's what is insulting my virtual environment. My computer does not have access to that unless I'm inside of that virtual environment. Now I'm able to say that this project is going to run. Wait till 2.7. And in another virtual environment, I can say, hey, use wayto 2.4. Use an older version of wait til use Wag Tail 1.13 if we wanted to. And these will never conflict with each other, which is really, really nice. So now what we need to do is wait till start my site and that's going to create a website called my site. I don't want it to be called my sight. I'm going to be making a website called Rocket Man. So I'm gonna do wit. They'll start Rocket Man. That's going to create a new folder called Rocket Man. But I don't want it to do that. I wanted to create a new project in New Wag Tail website. In the directory that I'm in. I'm already in a directory called Rocket Man. I've got a virtual of in here and I want to create this new project in this directory. So let's go ahead and do that. You can see that that was very fast. Ls Dash L. A. And we now have docker file Home folder. Managed up high requirements on txt Rocketman folder. That's where settings and static files and templates are going to go. We've got a search app and we've got our virtual environment. So now we can do pip install, Dash are requirements dot txt and all of that looks like it's already been satisfied to some degree, and now it's a runner server. We just type python managed up. I run server 0.0 dot zero Colin 8000 and it should say this you should see this alarming red tax, saying You have 89 un applied migrations and if we were to open up our browser and head on over to local host support 8000 we're going to see this air. That's actually a good thing. That means your website is up and running. So I just cancelled my server there with control. See, I don't just cancel your server so it doesn't run anymore. And next we need to provisional database. Because if we skip back over to where this error is showing up on local host 8000 we're going to see it says no such table wag tail core site. A table is a database term. This is looking for a database that set up in a specific way to get started, and it's not set up that way. So let's go ahead and set that up. The proper waste. Let's do Python managed at Pi my great, and we'll see this is going to apply all these different files and then if we rerun her server with Python managed up, I run server 0000 Colon 8000. Let's go back here and refresh your page. Wallach. We have Wait till installed using V envy or event. And then to get out of your virtue environment, you just have to type the word deactivate. You can see that down here. Sorry, that's at the bottom. But yeah, you just like the word deactivate. And that Rocket man ven seems to go away. Another way to do this is you can have your Web toe website already set up. So you've got Alice Dash L A. Let's just pretend that rocketman Vin does not exist. And we wanted to maybe use pip end. All right, so I just clear off my screen You can see which folder I'm in. I'm in the Rocketman folder at Last Dash L. A. You can see that I've got all my virtual end in here as well, so let's just ignore that. But we've got the entire wait till project in there. So remember, all I did was wag tail start, Rocket Man with the dot And that just installed my project into this current directory. Now, if I wanted to use virtual and I could do Pippen shell or pip engine stalling just into Pippen shell, you can see that is creating a new virtual environment using Python 3.7 point four. It's actually different versions Python than what I'm using, just slightly different and all right, we are inside of it and you can see that it's the Rocket Man virtual environment. We've got something that looks little bit different here before it said Caleb telling and my path to which folder I'm in. If I do P W. D. I'm still in my Rocket Man folder and I think you python Dash V I'm using a different version of python, which is neither here nor there. It just happens to be that Pippen have decided to use the latest version of Python for me. I have different python environments set up, so it did some magic behind the scenes there, but for you it would install the same version of pious on that you were currently using on your machine. And then, once again, once you're in here, you could just type python managed up. I run server 0000 8000. But as you can see, it's going to throw you an air because we're in a different virtual environments. The 1st 1 we created was a venue that's a different virtual environment, technically, and this is a pip end. Another virtual environment does the exact same thing, but it just it isn't aware of the things we've already installed. So let's go ahead and do Pip install Dash. Our requirements dot txt and this is just going to install wayto all over again. All right, that's all installed. And now I can run my server once more. Go back to my browser and I could just refresh because this is just from the V envy and it still works perfectly. So that's how we install. Wait till using a V Envy or Pippen. You don't have to use either of these. You can use virtual environments if you want. It's actually called virtual, and you can use docker vagrant. You can use anything you really want, but these are two fairly common ways, and I thought we would cover them since they're pretty easy to set up. And they don't require anything special, like docker or vagrants or any big downloads. Oh, and one last note. Just to get out of here. It's not quite the same as just typing the activate, at least not on my computer. I might work for you. Just type in the word deactivate doesn't quite do the job for me. So what I have to do is hit control. D. So I'm gonna take that out. Control plus D to cancel that and hit control D, and it just exits for me. So make sure you have a virtual environment set up again. It doesn't matter what kind you used. Make sure you ran your pip. Install our dash requirements a txt. Make sure you've got white telling everything installed. And once you see this egg screen, definitely feel free to move onto the next lesson. But don't move on until you see the happy wag tail egg loading screen. 4. Getting Started: all right. Since we're building a website together, we are going to need this same assets, the same file. So I already have a bunch of the front end stuff already done. A bunch of CSS and JavaScript is already compiled, and I'm going to give that to you. So you don't have to worry about doing all the front end stuff because this is not a front end course. This is about learning. Wakil CMS. This is not about learning Boots, Trapper foundation or Web pack or parcel about Js or anything like that. So you'll be able to download this file called rocketman assets. Does that by apologize if that's a little small there. But we have rocketman assets dot zip and you could just extract that and you're going to get front end stuff. So you get all the front end goodies all the JavaScript, the source and CSS and all that good stuff in there. Package of Jason. If you want to do the front and stuff, you can run NPM install and you'll just install everything. Uh, or you can just totally ignore all of that and we can go into the Rocketman folder and here . You're going to see you CSS images and JavaScript. You want to want to select all of these, Go into your actual rocketman project. So we we've got, like the rockem inventive from the last project even have a pit file because I used a pip environment as well. So let's go into Rocket Man. If your project is not named Rocket Man, it's going to be called whatever your project is. So in this directory here, this is where I installed my wag tail app. This is where I created my wait till website. This might not be called Rocket Man. This might be called your website. This might be called something completely different, but it's going to be the name of your project. Whatever you typed into your command line, then you're going to find a folder called Static. And we should already see CSS and JavaScript in there. Let's go ahead and just paste that in there. This is just saying a folder name CSS already exists in this location. Do I want to merge? Will replace that. I was going to replace all of it because I don't need to merge anything. I just need to overwrite that stuff. Now, on a typical wait till project, you don't need to do this. So all this is going to do is make sure that we are running, Ah, website that looks basically the exact same acts the exact same. Now again, just as a quick little refresher. Your front end stuff thes three here, Basically anything in the root directory of your rocket man assets that in that were in the zip file can go straight into your route project as well. So I'm gonna go up a couple directories here where I have my manage dot pie file and I'm just going to paste those in there. And you'll also notice now what I just did There was I showed all the hidden files. So if you're on a Mac, you're going to want to hit command shift and then, period, and that will show all of your hidden dot files. We're going to want to copy these as well. This is just a Babel RC file, and a basic editor can fix that. We're using the same type of inventing, so your codes going to look like my code get ignore so that you don't accidentally commit files that aren't really useful for your get repo and the node version that I'm using. So again, I'm gonna throw that into the route of the project. Here. The route is where managed up high lives and then CSS images and JavaScript all go into your static directory here, and you should be replacing CSS and JavaScript folders with the new stuff that's in the dot zip file again on a regular waggle project. You don't have to do this because you'll have your own design that you're working with. But this is just to make sure that you and I are both in sync at the same time. So once you're done doing that, or if you have decided to completely ignore that and work with your own design, that's cool, too. Let's head on over to the next lesson 5. Logging into the Admin: all right. At this point, you should have a base wayto website up and running, but you might not be able to get into the admin area. So if you don't have your website up and running, let's go into our virtual end. Whichever you're using. Maybe it's Docker, vagrant virtue and ven Pippen doesn't really matter. You just need to get into your virtual environment. So I'm going to get into mine using a event. Rocket Man vendor is what is called been activate. I was going to source that file and you can see that I'm now inside of it. And now to run my server going to do Python managed up. I run server and a quicker way is just zero. Colon 8000 and this will run the website on Port 8000. We can see it here if I refresh this page. Port 8000 and it's loading perfectly fine noses. The content management system. We want to be able to get into the admin areas. Let's go into the admin interface and how do we log in? Is it at men? Admission? Is it at men Password. We don't know what this is and by default, Wag Tail and Django and Python. They're all very security based, so you don't actually have a user. There's no way for you to get into this yet. We need to create our own super user. So let's go back to our terminal. Cancel our server with control, see, and I'll clear this. So it's back up to the top. And let's type Python managed up. I create Super User All one word I was going to ask me for a user name. I don't want to be Caleb calling. I just want to be Caleb. My email address is going to be Caleb at Learned wag tail dot com. Password is going to be whatever I want my password to be. It'll ask me to enter it twice because it's a good practice. Let's just go ahead and rerun our server once more. So what that did was it basically just created and admit account for us? So now we consign it. I have the user name of Caleb, and my password is whatever my password was. And wallah, I am inside of my way. Tell at men, and if you ever lose the link to your admin. It's always your website slash admin. That's the default one. You can always overwrite that a little bit later. And if you're just installing your first website, you can simply click on admin interface and it will take you there, and that is how you create a way, Tell administrator Super User. 6. Wagtail Walkthrough: Let's take a quick walk through through the Wag Tail CMS had been. Now this is very bare bones. I just started a brand new project, so there is literally nothing that doesn't come out of the box right now. This is everything that comes default with a brand new way. Tell website now out of the box, Wait till comes with many different components, but most of it can really be split into about five pieces, I suppose. Eso you've got pages which are here, you've got images, you've documents, you've got settings and then you've got users. But to simplify that, I like to really say you've got pages, images and users. Those are the three big ones. So this is your user interface. That'll tell you how many pages, how many images, how many documents you have you can explore through your pages And here if you have other pages, there will be a little arrow here. You can click into that and creates a nice little menu for you, or you just click like I want to edit the home page, Click the edit button and this will allow me to edit the homepage. There's a title field here. So this really only has one field, because again, this is a base. Wait till project. Ah, we can delete the page on Publish it Publishing submitted for moderation cause there's moderation available with Wag Tail so you can have a group of editors who can't publish stuff without approval. And we can also preview our page at any point in time. That's our content tab. In our promote tap. We have a slug. So if you wanted to be your website dot com slash about dash me or your website dot com slash contact for a contact page that's going to be your slug. Here, you can have a custom title that overwrites the default page title. Some show in menu options, which sometimes helps you create a navigation bar. Depending on how the developer set it up, you have a search description. So this is the search description that Google used to use in search engines used to use and still a good idea to have a search description in. There might not be available. Text on your page that might not be visible, I mean, but it could be in the head part of your page. Next, we have settings. These are scheduled publish settings a date to go live in a date to expire. For this, it requires a Cron job. If you're not a developer, a Cron job is basically a task that runs every X number of minutes or hours or days, and it will automatically check to see if you have a page that needs to go live or page. It needs to expire. This is really great for promotion. So if you have a page and you're trying to do, ah, Black Friday deal and it's literally just black Friday, you could say Black Friday for a 2019 was Friday, November 29th and we gets at the time and then the expiry date is going to be that exact same time, exact same day, but at 11 o'clock at night, You do, however, need that to be set up on your server. That Cron job needs to be set up by someone who has access to the server. This is not automatic. Then we have a view live button up here. This is what our page currently looks like again brand new website, so there's nothing on there. We have different privacy modes. So is this page public to everybody? Is it private but accessible to only people who are logged in? We can say yes to that or private, but accessible to people with a following password. And then you could set a password for or is a private and accessible to users within specific groups. Well, we only have the two groups here. We've got moderators and editors. We could create other groups as well and say only certain people can view this page. Next. Let's quickly take a look at search. I'm just in a search for home. Yes, I want to leave the page. It's gonna find the pages for me. It'll find images, documents, users, all other things that it can sort of look for. It really only found one thing here, found a home page called home, and here we can edit the page we can view alive. We can add a child page. We only have the one page type available, so it's gonna ask us to create a home page under homepage. That's a little awkward, so we're not going to tackle that too much. But if you have pages. And maybe you created a page in the wrong place. You can move them. You can copy them, you can delete them unpublished. You can even look at page revisions on this page is and have any revisions. But if I changed any text on the page, if I change any of the content, it will show up in my revision section. Here and again, this all comes default with white tail. Next, we have images in here, so let's go ahead and add an image. So I'm just going to upload an image called Rocket Launch One. I believe this is courtesy of Space X. Upload that and done just like that. We've got an image in here. We get a you, Earl, so we can copy link location pace. I didn't a new in a new tab, and you can see that this image is pretty big, actually, but there it is. So we've got image hosting built in automatically. You can change the image. You can add tags, so if you want to search for it, you can add tags. You can change the title of it and wait till comes with this really cool idea of a focal point. Focal point is, you know, when you upload an image of a bunch of people and, like someone's face gets cut off and you don't want that to happen well in Wag Tail and you can select the part of the image. That's most important then, whenever Waittil tries to crop the image for whatever page you're working on, it's going to get as close as possible to this section. You're saying this is the important part. Move the image over here. So when it crops, it gets a little closer. It's really, really nice. Go ahead and save that image, and that's really all there is to it. I guess we could also delete the image or remove the focal area if we wanted to. Let's move on to documents. Documents are basically the exact same thing as images, so I'm going to add a document in here. Rocket Man assets dot zip doesn't have a preview because it zip file. Let's go ahead and update that click on documents again, and we can see that we have a document in here called rocketman assets dot zip. I can download it directly, or I can click over here. Grab this link. So this is a link Copy link location. Let's paste that in here, and sure enough, it tries to download it again. So now we have file hosting. One thing to keep in mind is these are not private, so don't host credit card numbers or anything like that on here. That is definitely a bad practice. Next, we have settings, inner settings. By default. We have users, group sites, collections and redirects. I have one user in here called Caleb. I've gotten email address first name, last name password. I can change my password in there, and I've got roles. Moderators and editors. I'm not going to change any of those. I am a super user. I mended men. I have more rights than both of these, so I don't need to change any of those going back to users. You can always add a new user. And in here you can say if they're an administrator or not an administrator or if they're not an administrator, should they just be part of a certain group? Groups come with certain permissions, and there's password rules, all sorts of good stuff. Okay, let's talk about groups. We've two groups in here. Editors and moderators. Let's take a look at editors. What can an editor dio can access? The way Telemann cool but cannot add redirects or sites or collections groups or users can't change? Any of them Can't delete any of them. They're page permissions, they can add. They can edit, but they can't publish the can't bulk delete, and they cannot lock pages. Collections are basically like folders for your images, so you can put all of your images or documents in inside of some form of collection. And then you can add permissions around that as well. Images have collections as well. We've only got the one collection called Route, but the exact same thing applies there. All right. Next we have sites so you can have multiple sites hosted on one single instance of wait til Right now we just have the one called local host Port is 80. I'm gonna change up to 8000 because that's what my website is running on. So if I do this local host Port 8000 that's my website comes in change. That's to 8000 just to match it. Ah, site name I'm gonna change us to rocket men just because that's what this site is going to be called, and you can even change the home page for it. So let's choose a different page. We could We can't because we only have the one page because this is a base install of Wait till if we had another page, maybe a different type of home page. We could swap that out entirely. And is this the default site? While we only have the one site. So yeah, we're going to say that's the default site. Next, we have collections again. Collections, air, really just folders where you can put images and documents in there. And then you could put permissions around that so certain groups of users have certain access to certain collections. I'm not going to add a collection because it's not that useful to me at this particular moment. Ah, summits and move on to redirects a reader X A really nice. You can say anyone who's lining on, Let's say your website dot com stash my 2019 offer. So maybe this is some sort of landing page, and I want to redirect them to a different landing page, a different service, maybe a different website entirely. I could redirect everyone to google dot com google dot com. So anyone who goes to my 2019 offer actually, this is not going to be there. Anyone who goes to my website dot com slash my Dash 2019 dash offer is going to get redirected here, and you can set up all these different redirects if you want. You can even choose a page internally inside of wayto to redirect to if you wanted to. And lastly, we have account settings. You can set a profile picture. You can change your email, change your password notification preferences. There's language preferences. If these are all enabled, which they are by default, you can set your current time zone Micro and time zone is not even set, so I probably want to go and set that a little later and you can even change your name. So these are all the base things that come with Wait till right out of the box, and every single component is extendable to some degree. So, yes, we only have one page right now, but we can add hundreds of pages later if we wanted to. Sure, we only have one image. We could add more if you have a contact page. If a developer has made a contact page for you, you'll see form submissions in here as well in your menu. If you have snippets enabled to reusable pieces of data, you'll see a little section in here in your menu called snippets, and you might see other custom things that developers will put in. There's bone, but out of the box. This is what you get with my tail. 7. Editing the Home Page: All right, let's go ahead and edit our home page. It's actually work with some code here, so I'm going to get into my virtual environment. I'm using vendors, so I'm gonna source my rocket man ven been activate and I am inside of it. I just want to run this website. So I'm gonna run Python managed up high run server on Port 8000 and we can see here if I refresh, it works. But if we actually go to the home page and try to edit something on here or even if we try to view the life page, we haven't done anything yet, So let's let's actually get into this. So we're gonna add a number of fields here because right now we only have the title field in our main content. We've got promote fields and settings field, and that's great. Wait till gives us all of the's options, but we want to add more. We want the home page to have a little more spice. So let's add like the home page banner. Alright, so I've just opened my editor and I've opened up the project, so I've got all my files in here you can see them on the left hand side. I'm just using V s code. By the way, you can use any editor. Doesn't really matter. So I got my front end stuff in here. I've got a home app. I've got media. This is where my images and documents and stuff We're going to be uploaded. Uh, not rocket Man. This is where my setting, static files and temples were going to be. We're going to be doing a lot of work in there. We've got the virtual environment, which we don't actually need at all. It's not for editing purposes, and we have a search app which we're not going to be using. So the first thing we want to dio is open up home and go down to models dot pie, and we're going to see in here. There's actually nothing in here at all. And if we take a look for base dot pie and we scroll on down, we see installed APS, we have home and search and a bunch of wayto ones model cluster tag it and a bunch of jangle APs all installed. This comes default with your wag tail site. The two that we want to pay attention to hear our home in search and actually more so we want to pay attention a home. So this home H o M e matches this app right here. And if we wanted to get rid of home page, we could just comment that out or delete it entirely. Save restart our server and we won't have access to the whole map anymore. We'll get more into this as we go through this course. But if you added another app in here, such as services, you just added in there like that we'll talk more about this little bit later and and how to start a proper Django and leg tail app in our home slash models dot pie, we have just a single class called home page because the APP home is enabled. Wag Tail and Django say, Oh, this is a classes is a page. Okay, well, I'll automatically provisional little bit of space in our database and structure properly for your homepage and passed just means it's not doing anything. So what we want to do now is we want to add a few fields we want to add maybe some lead text. Ah, button button text and maybe some background image for a banner. So let's just do this one at a time. Let's do lead text and this is just going to be a very simple char field, a character field. So this is getting into jangled. And so this isn't wag tail. This is Jenkin. We can tell because this is coming from models dot We can see if we look at the imports here is coming from django dot db import models. So we're going to create a new char field or a car field. V s code is nice enough to show me all the different options in here. The 1st 1 that we really want those max length were instead of maximum length of about 100 40 characters just for this lead text, we're gonna say it can in fact, be blank. And maybe, let's add some help Text help taxes equal to a subheading text under the banner title. Something like that. We can always adjust these later as well, and I'm going to clean this up a little bit. Put these on separate lines so it looks a little nicer and just like that. We have a new field. We have to run some migrations and stuff, which I'll talk about in just a minute. Let's go ahead and add our next field. Let's add a link to another wag tail page. Now we only have the one wag tail page, so this is not going to be super applicable right now, but we're going to see how this works. So let's create a button. And for this, we're going to use models dot foreign key. We're going to select a foreign key from the WEG tail core. Dot page Blank is equal to true. We're gonna say that's optional. No is equal to true, so it can have absolutely nothing in the database. Let's give this a related name of just plus basically ditched the related name. For the most part. Help text is going to be Live that up. Select an optional page to link to this is going to be for R C t a button. We don't really know what that looks like right now, but I'll show you in just a little bit and we need one more in here just to satisfy Django , cause again we're still working with Django here on delete. What do we do when whatever Paige this is being linked to is deleted? What we're gonna say, models dot setting all and let's create some button text again. This is just going to be regular char field, so models dot chair Field give this one a max length max length of 50 so the button text cannot be too long. Let's give it some default text, though Read more can be blank. We're gonna say no. This always has to be set, even if there is no button selected were going to say this button text has to be selected. There has to be filled up and let's add some help text button text and you'll see what all this looks like once we get just a little bit further. Now, lastly, we want to add an image in here. Let's do banner background. Image is equal to models out. It's another foreign key, but this time we're going to be linking to wag tails. Images dot image. This is their default image, so if you're using a custom image model, you're going to use a different value in here. But we're not. We're just going to use wag tails standard images, so we're going to link to this one. Blank is equal to false, but no is equal to true. Which means in the database this field, the column called Banner Background Image is allowed to be empty and blank means When you go to save the page, it actually has to have a value in there. So in the database, this can have nothing. But when you fill out the form, it has to have something. That's the difference there related name is equal to. Plus Cousin was gonna throw that away. Help text the Boehner background image, I suppose, is what we can say and on delete. What are we going to dio? Let's say model is dot set No. Now let's go ahead and save this Open up our terminal. It restarted. Everything looks OK, but when we open up your browser, we are going to see when we refresh our page. It says that there's no such column. Home is the APP. Home page is the class name. Lead text is the column name in a database. So how do we make this existing database? Well, we're not going to do anything with the database in particular, we're going to let Django handle this for us. And so this is really, really easy. All we have to do is clear. Let off. Let's do python manage dot pie make migrations. And what this is going to dio is really just create a set of instructions for how we want to restructure our database or how we want to alter the database. So it made a new migration for home called 0003 auto blah, blah, blah bunch numbers in there. And if we open up our editor go into our migrations file, we'll see 0003 auto, blah, blah, blah. And as a bunch of instructions in here, you don't need to know all this stuff. Django handles all of this for you, which is really, really nice. But at this point, all we have is a set of instructions. We haven't actually told our database to use this set of instructions. So our next step is going to be python manage dot pie, my great, And what this does is it takes all the untracked migration, so it's going to store which migrations have already been applied in the database. That's going to say, 00003 Auto in the home app has not been run. Let's run that. Let's let's take all the instructions that we see in here and that's alter the database. And so let's do that. He had enter. The migrations were run. Okay, and now we can rerun our service. Let's do Python managed up. I run Server 0 8000 Open up a browser again, Give us a refresh and our page loads. But we have a problem. We wanted tohave lead text, ah, button, where we could select another wag tail page, some button text and a background image. And then, if that's showing up, it's not in promote. It's not in settings. It's not in content now. Why is that? Well, there's actually good reason for this is just because you have set fields in your model. Just because you want to alter your database and store some data doesn't necessarily mean that you always want that data or that field to be exposed. You might want some data in here that can only be programmatically changed, and so because no Web app on the face of this planet. I can tell you what you're planning on Lee. You know what you're planning. This is not going to expose any of these fields for us. So we now need to expose these fields and in wag Tail. This is really, really easy. So on every page and I'll just scroll up here, you can see that I'm still on the homepage. On every page we get content panels is equal to page dot content panels. This is a page class provided by Wag Tail That gives us our title and whatever defaults weg wait till wants to give us. And these are the content panels. And so we're just gonna say, Give us the existing content panels. Plus, let's append a list to this. Now we have different panel types in here, so we have to make sure we use the right one. If we use a field panel that's just for regular text. We've got lead text in here, and that's just a regular text panel. Wait till will figure out that it's a char field and it should just be one line of text. It's a regular html text input the next one for a button is going to be a chooser panel and we'll have to import these in just a second. And that's all we dio. And so this lead text matches this lead text up here and this button matches this button right there. So now we need to add. But in text and banner background image, let's go ahead and add one more field panel. Field panel button text is what it was called. And then we're going to add one last one image chooser panel banner background image. Now if we save less and check out our terminal is going to complain. Yeah, it says field panels not defined. If you ever see this ever, it's just saying that basically what you're looking for has not been hasn't been imported into your page yet or into your python file. Let's go ahead and import these from wait. Tell dot admin dot edit handlers that those panels those air called edit handlers Let's import the field panel and V s code is nice enough to try to auto feel this for me Field panel and page chooser panel and the image chooser panel actually doesn't come from Edmund at handlers. It comes from wayto dot images dot edit handlers Import image Chooser panel say that it looks like everything here is working nominally, which is great news. If, for whatever reason your server has died, you just need to rerun it. Just run. Python managed up. I run server 0000 8000. Let's go back here and refresh your page and look at that. We have lead text button button text and an image. This is pretty nice. So we're gonna change our home title to rocket and maintenance because that's what this website is going to be all about. Maintaining rockets. Let us handle the dirty work. You focus on launching into space. Something like that. A button. This is pretty nice. This gives us a what's called a page chooser panel. So if we had more pages, we could go and explore more. We'll see more of this in the future. We really only have the home page right now, so actually we don't need that at all. So it's clear that choice the button text. We said it always has to be there, and the default is read more and a banner background image. I'm going to upload a new image here. Hit, upload Cool and let's go ahead and publish this page. Now when we view life yet nothing has happened again, Wade tell is not a 10 minute website content management system. This is for customized websites, and at this point again, wait till Django and Python don't know what you're looking for. There's no pre defying theme here. This is just what wait till came with. So now we need to go and edit our home page itself. So it's open up our editor. I'm gonna open up V s code. Let's go ahead and close that file. We don't need it. And if we look in our home app and then in our templates directory in there, we've got a home folder which has a home page and a welcome page. The home page is the one we want to edit in here. It's extending from based on html. We'll talk more about based on HTML. I believe in the next lesson is going to load static. It has a body classing here, some extra CSS if we want that and where we want to put our main content. So just as an example, let's do this H one hello world And all we're doing is overriding this content because before what we had Waas it was including home. Welcome Page was including all this stuff in here. We don't want that. So we're just gonna say Hey, overwrite it, save it. Miss Refresher Page says Hello, world. So now we're actually editing our home page template at this point, which is pretty great news. Let's go ahead and get rid of some other stuff in here. We don't need extra CSS at all or body class. We may decide to introduce that Ah, little bit later. Reintroduce that a little bit later. But for now, we're we don't need that. We just need to change the main content section and because we're not using the static tag , we can get rid of this. We'll see a little bit more about static a little bit down the road as well. So let's see how we can get the page title. So we want to change hello world to say, rocket maintenance. That's that's the title of this page rocket maintenance. So we could just do Curly bracket, curly bracket page dot title. And if we refresh our page here, says rocket maintenance, that's pretty nice. Let's go back and let's see what some of our lead text looks like, so page dot lead tax. Now, if you're wondering, Caleb, where are you getting lead text? Where you getting title from? Let's go back to our models. Lead text is defined here, so because we're on a page template, you can use page dot That's the page object, and then you can use lead underscore text. That's our custom field here. Lead text. And if you're wondering, OK, love okay, yeah, that'll make sense. But where did you get title from? That's a great question. Wait Till comes with a lot of goodies already built in. So if I right click here, go to definition. Not every editor has this feature, by the way, but this one does, which is really nice. You can always check out The source code is well, get hub dot com slash wag tail slash wag tail. This is the page that we're using, and it already comes with a title field. It's got a regular charge field in there with a verbose name. Max length help text. It's got a draft title slug. It's got a bunch of other things in here, but we got title because we're using this class in our python class. So in the world of Python, we can inherit from other classes and get all the goodies that is going to give us. In this case, we're inheriting waggles page class, and it comes with Title, which is pretty much the exact same exact same thing as us saying Title is equal to char field, yada, yada, yada all the fields in there and then accessing it on her template with page dot title. All right, let's say this and the see what our lead text says. Look at that. We've got to delete text in there. It's tiny and it's not styled it all, but it is now pulling in our custom fields. All right, so what I'm gonna do here is delete this and I'm going to put im in a paste in the front and stuff, and then we're going to at layer in the wait till template part. All right, so I just pasted in some front end stuff there. I haven't done anything with the wag Tail work we've done yet. So if I preview this yeah, it doesn't look great. We'll add the styling and stuff later. I think it's in the next lesson. We're gonna add all the styling, so this looks really good, but for now we want to work with some of this logic. So, like our page title up here should be whatever our page title actually is so we can use curly braces for the mustache Syntex title. We've got lead text in here. We can do page dot lead text. We've got button texting here we can do, do do do do do do do page dot button underscore text and you for wondering where I got that one from that comes from but in text We also have lied text here, which is allowed to be blank, which means this is optional, so we need to make sure that this is optional. Now we can do some if statements in here if page dot lead text and if and this is just regular Django template ing, you know, if your brand new toe to Django and wag Tail, this is basically how we add logic into our page. We used a curly bracket, followed by a percent sign, and for the end of our logic, we use percent sign and then curly bracket. And if we ever just want to display information, we just use to curly braces together. So let's go ahead. Preview this. We can see you rocket maintenance. Let us handle the dirty work. We need to check to see if that button actually exists. Set that girl and maybe a background image. So before we even say that there's a button, let's go ahead and do if page dot button and again, I just got that from do to do to do or foreign key. And I'm gonna invent this because it looks nicer that way. And for the link, we'll give you page dot button that you are out because Button is a foreign key. So this is going to link to another page. Maybe it's Page I d. Number four. That's what is going to link to because that's how foreign keys typically work. So is going to again Look up Page I d. Number four with this page dot button, and then he's going to give us our u R l whatever that's going to be now that's not going to show up with anything. So if we preview the page, are but it actually goes away, let's change that. And let's just set the home page publish Angela. It shows up hard to see. We'll fix that in a bit, but it shows up and the link. If you look at the bottom left, says local host Port 8000 that is, in fact, the homepage. Lastly, we need to set an image in here. And so this is the first time we're going to be working with images, Certainly not the last time we'll talk more about images in depth as we progress through this course. So the first thing we need to do is we need to load. What this is saying is, Hey, when you load this template, Django, when you load this template, know that it extends based out html. We'll talk more about this and blocks in the next lesson. Ah, and there's a section in there that we want to overwrite called content, but also just so you know, we need to load way, tell images, tags because we're going to be using a tag that Wag Tail gives us and that allows us to use a template tag called Image. And we tell it as its first parameter which image we want to you. So let's do banner background image. Our second option is going to be How do we want to create this image? It's going to cropper image. No matter what we dio do we want to fill it with certain with and heights. Do we want to say there as a max height or Max with anything like that? We'll talk more about that later. Again, I just want to show you this sort of just to get your feet wet with images and we're gonna say Phil 1600 by 800 as BG image. And now we have access to BG image as a variable. So let's type curly braces. BG underscore image dot u R l Let's refresh our page. We'll see that we have a u R l up here. If I go ahead and copy this paste that into our browser. Look at that. It made the image for us. It cropped it. So this was originally a square image I believe and how it is 1600 by 800 and so just handled all of that for us, which is really, really nice. Now we need to take this u R l and apply it as the background. And so I already have some styling in here and I want to add this background image you are . L save that refresh and we consort to see that the images sort of there a little bit again . The styling will come in the next video, I believe where this is actually going to start looking like a website. There are also other fields that we can add to our model. There are things like Stream feels that we can add There's validation and things that we can add which we will add a little bit later. Just not right now. At this point in time, what we've done is we've modified the default weg tell home page we've given it for extra fields, so it already comes with things like title. We've said we want to give you a lead text, a button button texts and a background image. Now, if you're ever applying this and you apply a field panel to either the page chooser or the image chooser. I'll show you what this is going to look like in the ad men when we edit our page. Our button now becomes a drop down on all the pages in our wait till site, and the image chooser also becomes a drop down. Of all the available images that we have on our website. That's not really what we want. That's not a nice user interface. So if you ever see that, chances are you're probably just using the wrong chooser panel. Field panels are great for just regular text page shoes or panels are great for foreign. Key is going to another page as we see here, Foreign key going to another page, we said this could be any wag tail page, any future wag Tail page, we could create a swell another text panel and then an image chooser panel for our background image. And again that just allows us to choose and upload images sort of dynamically, which is really, really nice. Next, let's go ahead and work with the base template and make our website actually look like something 8. Working with the Base Template : in the last lesson. We worked with the homepage and the home page template a little bit, but let's go ahead and actually structure based on HTML template. Now, if you don't know what the based on HTML template is, let's actually look at that first. So I'm gonna closed down the files that had opened from the last video open up base dot html and you can see it's in my Rocket Man folder slash templates. So if I close all of these and reopen this one, we can see I've got Rocketman templates and then based on HTML. And the idea is that every single page eventually gets derived from a base template. So instead of having to declare HTML five class node.js title meta data all this other stuff, we don't have to do that every single time. Instead, what we can say is, Hey, everything else is good, it's fine. But let's just override this content block or let's just override this body class block. There's no need to add JavaScript every single time because we can just add it into our based on HTML, and it just knows. And so as an example, if I open up home page dot html. We have a section in here called Block Content. We also have a section in our base dot html called block content. And so those two match, which means anything in here because we've said it. It extends based on HTML. Anything inside of our block content. All this stuff is going to overwrite Whatever isn't here. It just happens to be that there's nothing in there right now, but we could give it a default. All we have to do is say hello world, and this will automatically be over written on any page that is using this block content section. So there's just a couple things I want to clean up in here just to make this website a little better. We're not going to check to see if JavaScript is enabled or disabled, although you might want to keep that. We're not gonna worry about the title or or the description or anything like that. For now, we have a global style sheet, and this is looking for static. CSS rocketman dot CSS Now A couple lessons ago we saw this loaded in here says Load static , and we said how we don't need it. We're not going to use this time. We are. What this does is says Hey, in the jangled wag Dell'Apa, you have a static folder somewhere, Whatever you named it, because we can change the name of that folder. We can even actually specify that the folders, not local, could be stored on Amazon s three or digital ocean spaces or something like that. But what we're saying here is check the static folder. Then there's a CSS folder, and there's a file called Rocket man dot CSS. Now, let's see if that actually exists. So in a Rocket Man folder, we have a static directory, actually just called static in, Which is funny way of naming that. Then we've got a CSS directory, and we've got bootstrapped out CSS and bootstrapped on CSS dot map. So this isn't called Rocket Man in our project, this is called bootstrap dot CSS. Now, if you don't have bootstrapped on CSS in your project, you're going to want to go back a couple of lessons and download the project files. I have actually provided these files for you already, so you just have to copy and paste them into your static directory. So if you don't have those, you're probably gonna want those. And then we have some global javascript down here, so let's take a look again. This is saying the sources static wherever that might be. We just know that it's right here. Fuller called Js. And this one is rocketman dot Js. Nope. For whatever reason, I decided to call that index dot Js and that matches right in here. Let's go ahead and give this a quick save and a preview. So I'm going to save that file, go back to our homepage and Walla. It looks not bad. We don't have anything else to display on here, and that's okay. But we've gotta wait till user bar, which is really nice, because if you want to add a child, Page added this page showing explored or the wag tell a man you can do all those things and that only shows up if you're the admin. And if the wait till use a bar is enabled, which is right there. So if you ever wanted to get rid of that, you just delete that save refresh and that user bar goes away. I like it being there, though it's pretty convenient. Let's go ahead and clean up some stuff that we don't need on this website. But, you know, it might be useful for you in the future. So if you ever want to modify your body class, you might want to keep this blocking here. Because, for example, if we go to our home page, we could throw this in here and we could call this home page something like that. And that will essentially do this when the home page is loaded. But for this website, we don't need that because this website is very simple. So I'm going to get rid of that just because we don't need it. And then for our content section, I actually just want to wrap this in a main elements domain. Give us an idea of content. Ah, the classic Do some bootstrap classes here. So, container margin, Why? Access for And the role is going to be main nice and simple, and our content is all going to live inside of there. Go ahead and preview our page and oh, okay. Well, that sort of broker banner, you know what we can do is that we can actually create a new block in here. Let's create a new block called Banner So Block Banner and by default is going to have nothing in there. And let's end that block. And what this is going to do is allow us to use this block on her home page, and it's not going to be wrapped with whatever bootstrap styling is going to come with the main element. We're going to actually bypass that and inject it into here instead of into here. And this is a really nice part. All we really have to do is change this one word here, two banner. So this now matches that, and at the end of our block, we can give it a name or we don't have to give her name. I like naming it. Sometimes I don't name it. It's sort of optional, but we're gonna change out when Do Banner as well. And so if we zip this up, we can see Block Banner a bunch of stuff inside of it. It's 25 lines of code in there, and then we're ending our block banner. Let's go ahead and save that refresh our page now our banners up top and we have a content section down here. So if we inspect our page due to do, we can see we have a Jumbotron in here. That's our banner. That's part of this part up here. Ah, and then we've got a main section scroll down. We've got a main section down here. This yellow highlighted parts. But there's nothing in it. But that's where we're going to put our future content. So that is working with Based on HTML, we have modified the home page a little bit as well. At this point in time, we can also go back to our home page and do a little cleanup. We're not using the welcome page at all. We can actually look through her whole project for welcome page dot html, and we've got one in here that's coming from our site packages in a virtual environment that's just the default, one that comes with Wag Tail and the other one that comes default with Wakil. That's not in our project, not what we see in the left hand side here. So let's go ahead and delete. We don't need that anymore, and there we go. We now have a homepage with a banner. It's got a background image. It looks pretty nice. We've got a title of best lead text and we've got a button. A call to Action button. 9. Services App and Pages: up into this point, we've created a home page. We've modified the based on each email template, and we've loaded in some some new JavaScript and CSS, and now we want to add some other page type. So let's go ahead and create a couple service page text. No, for this we are going to need to. Source Are rocketman ven have been activated? Get inside of it if you're using Pip and it's just Pippen show. If using Docker, it's docker exact I t. Whatever your container name is. Bash, That'll get you inside of it and then you can run. Then you can run. Python managed up. I run server for 8000 40,000 just the default port that US developers tend to use. It's actually nothing special about that. And now when we refresh your page, okay, our servers working. And at this point, we actually want to create a new app. So we could theoretically, I don't see why we couldn't. We could put all of our pages into home slash models, not pie. That's a weird naming convention. We're saying all of our pages need to be in the home APP services need to be in the whole map. Contact Page needs to be in the home app. Miscellaneous page needs to be in the home app that doesn't make any sense at all. So we're going to create a brand new app, and this is actually just a jangle management commands. Let's go ahead and cancel our server and we're going to type. Python managed up. I start up services. And if we take a look in our editor, we now have a folder in here called Services. Has a bunch of files in your views. Tests models absent Minute Dot pie and Migrations folder with an empty and it dot pie file in there as well. That's a standard jingle app. What I'm going to do is delete a bunch of these files. We don't need views. We don't need tests and we don't need at men. My tail takes care of a lot of that for us and the tests. If you want to write your own test, absolutely, you can. We're not going to be doing that because we're not doing anything super crazy With this wag tail site, the remain one is going to be models dot pie and We're also going to want to open up our base top high. Not that one based on pie. And where we have our installed APS dress underneath. Here, let's go ahead and give it services. Don't forget to put it in apostrophes or quotations with a comma because this is a python list. And this name, all this does is reflect this name right here. That name, these two names they have to match. So I'm gonna save that file, Closed that down. And I am back in services models, not pie. So, in a regular Django app, you know is that I deleted the file called views dot pie, an irregular Django app. You have models, views and templates. Well, we're gonna keep the models file, but the view we don't really need that with a wag tail site because a wag tail page will combine the view and the model at the same time. It's actually less to manage, and in my opinion, it is a really, really nice thing. You can see everything that's going on on a page in one single file versus in a Django application, you have to switch between your models and your views. We're also going to keep aps dot pie just for a good practice. Keep your innit dot pie file that tells Python basically to include the other files in here , even if it's empty. And keep your migrations and the Empty Unit file inside of that as well. Next, let's go ahead and treat to new page classes. We want to create two new pages, so class service listing page is going to be a page. We have not imported this yet, so let's do pass and let's do class service pages, just a standard page. We don't know what's gonna be in that either. So let's also pass. This does not exist. This is not a variable. This is not an import. This doesn't exist yet, so we need to make sure this exists. So from Wag Tail Doc or dot models import page. And that's really all we have to dio. So now, at this point, we could run python, managed up high, make migrations. We could also run python, managed a pie migrate, and it would create a migration file that has the service listing page and the service page in it. And then we could start actually adding these pages to our way telling men were not going to do that yet, though we're going to add some stuff to this first. So our service listing page Let's go ahead and add Maybe like a subtitle. So we already know that Wait till comes with the title field That's inside of page. We don't need that because it's already there. Let's had a subtitle, though, and we're gonna say the subtitle could be fairly long. Let's do models dot text field can be blank. Yes, we're going to say that basically, this is optional. This does not need to be there at all. So when you save the page, can the subtitle field be empty? Yes, that's what we're saying. We're gonna say max length of at 500 character, something like that. Now, remember when we runner migrations, this is not going to show up on our page when we go to edit a page. Yes, this will be there in the background, but it's not exposed, so we need to expose it with content panels. We do this by saying content panels is equal to page dot content panels, so we're going to take whatever wag tails already giving us. That's the title field. We know that much, and we're going to append a list to this. And in here, we simply just want a field panel for the subtitle. And again, this subtitle Field name matches this one in the Quotations. And it doesn't matter if it's quotations or apostrophes that just needs to be some sort of string type, just as an example that switch it, it doesn't really matter. They both worked perfectly fine, so that's cool. So our service listing pages Now we're going to have a subtitle now in our service page. We're going to want a little bit more. This is going to be a detail page, and so on her detail page, maybe we want some sort of description. Maybe we want a link to some sort of internal page as like a call to Action Button or maybe a link to an external page, something that's outside of our wag tail site. That would be the U. R L. To a call to action button. We're gonna have ah called action button. So we need somebody texting there, and that's ah, it's also give us an image service image, so we know we have five fields in here. So the description this could be somewhat longer. So let's do a text field instead of a chart field. That's the difference. HR Field. This one is typically used for shorter amounts of text and text. Field is used for larger amounts of text. Blank is equal to truce. We're gonna say that's optional and a max length of 500 characters. Now we've got internal page. We did this with the home page already. We need this to be a foreign key so foreign key to any type of wag Tail page Wag Tail core dot Page. And this actually looks a lot like from wechsel core import page. And if we open up our base dot pie, we can actually an app in here called Wag Tail Core. So what this is doing is it's looking through wag tail core, and it's looking for a page model. That's what we're going to be able to link. Teoh Blank is equal to true. We're going to say this is optional. No is equal to true, so it could be stored as absolutely nothing in the data base just a no value. The related name for this particular purpose is not important. So we just use a plus symbol, help text select an internal, wait, tell Paige and lastly on delete to satisfy jangles needs. We're gonna say models dot sentinel. We could do that because no is equal to true Sentinel is allowed. Next, we have an external page, so maybe someone has called action on their page, and that button is supposed to go to a different website. Maybe it's app dot my website dot com instead of just my website dot com slash sign up. Who knows? There's lots of different reasons for this. This one is really easy. This one is just a ul field. I'm gonna say blank is equal to true and we don't need to specify no is equal to true, because when it comes to regular text fields, all we're going to dio it's a either it has an empty string or it has something in it like that. We're not going to say it could be No, that's just a good jingle practice. We've got some button Texan here. We did this with the home page as well so models Dutch Hartfield Blank is equal to true. Do we want that button text to be optional? We could do that. I don't see why. What we couldn't do that we can always check to see if there is, but in text. Otherwise supply default text. And because it's HR Field, we do a max length of 50. That's pretty long for Button, but we've got that and then a service image. This one needs to be an image. So we do models dot foreign key, and this is going to link to wag tail images dot image So again that goes to the wait till images app and is looking for the image class. We're not using a custom waittil image. We could specify our project to use a custom. Wait till image, but we're not. We're just going to use the regular wait till image right now. We're going to say no, it could be true, So it could be absolutely nothing in the data base, But blank is equal to false. So whenever you save the page, you need to have an image selected on delete. And this is just an example of how these property names don't really matter the order, while the 1st 1 has to be there. But these other ones these air keyword argument so it doesn't really matter which order there in So on. Delete is equal to models dot I said No help text. This image will be used on the service listing page. I just know that because I've pre planned this and will be cropped to, I think it's something like 570 pixels by 370 pixels. We'll probably also use it on this page as well. Yes, it will be cropped 5 17 Ah, on this page, There we go, something like that. It's just helpful. Text is what it is. And of course, we need a related name of a plus sign, and that's it. Next, we need to add our panels. But let's do that in just a sec, because I want to show you a quick little thing here. So let's open up our Edmund and it's not currently running. Let's run Server 8000 field panels not defined. Okay, Cool. That is expected from Wag Tail dotted men dot and it handlers import field panel. We'll need more of these later that will satisfy this particular error models. That foreign key is not defined. Yup, that's because that would have been a typo. Models dot foreign key. There we go. Related. Named again. This is just catching all my terrible, terrible typos related. Named is just related name. I had to manually restart that one. No errors. Okay, cool. So now let's open up our admin. We have a page in here, We have rocket minions, We can add a child Bage, and we can add a home page, a service listing page or a service page. So let's add a service listing page. Oh, no, we got a 404 and this is the air that I wanted to show you. The wag Goldman is going to try to gracefully handle this problem. Basically, what this is saying is on this page, this isn't actually using any data from the database. This is just going through all the registered APS and checking for pages which in this case , is going through the service app. That's the name here, services AP and is looking for a service page and a service listing page. And so it's not actually using anything from the database, But at this point, we click service listing page and it says, Oh, no, something broke because it's asking for data from the database and it doesn't have it yet. We have not made any migrations. So just like on the home page, we're going to create some new migrations. So it's you Python managed up. I make migrations. And again, this is just going to create instructions for our database. Next, we need to do manage that pie migrates to actually apply these to our website. Let's go ahead, Runner server once more refresher page and wait for it. Bam! There it is. We have a service listing page. We're gonna call this services. We can give it a special slug. Call it services, I guess. And a subtitle. Here are all the services we can perform on your rocket because apparently this is a start up with a lot of money. Let's go ahead and publish that and we can view this and we see template does not exist. Now let's just hold that there for a second to come back to this in just a moment. I'll leave that tab open when I come back to here. I can now actually go into my pages. I got rocket maintenance, A page underneath that is the services page. All your pages should live underneath your home page, and then we've got an edit button. So I go back. We've got Arrow because there's now child pages and we can view the services. Now let's go ahead and add a child pager. That's Atty Service Page. But if we look at this, this isn't showing anything that we've actually added. We've added a description, internal page, external page, but in text and his service image. But we haven't added any panels again. Wait Till and Django, they're saying, basically, you have these fields, but we don't know if you want them all exposed, so you're going to have to expose them on your own because again, we don't know the logic behind your application. So you're going to have to manage that, and that's actually a very preferred way of doing things. So I'm just in services models that pie. I'm under the service page and in here I'm going to add content panels is equal to page dot content panels, plus a list so again. We're just gonna say, Hey, let's to find some content panels in here. But let's also grab that wag tail a title one that the one that comes default with wagged. Oh, and now we have to add a few of these. So let's go ahead. Copy all those I m. Pei citing here. So I've got a list to reference from comment That case, it does actually throw any problems for me. So I've got an internal page here, right? I want to use a field panel for an internal page. Is that right? I've also got description for the internal page. No, this is a foreign key to a page. I want this to be a page chooser panel. Otherwise we're going to get a drop down list of all of our pages, and that's no way to live life. So page chooser panel and let's go ahead and add a regular field panel for our description . Next, we have an external page. This is just a ul field. So external page is our your URL field right in here you are all field is really just a text field with extra ul validation, so that could be a field paddle because it's a regular field. Next, we've got button text again. This one is just a regular text field. We can see that up here. It's just a char field, and then we've got a service image. This is a different one because it's a foreign key to an image we don't want to drop down. List of all the images we can select. We wanted to actually have a image chooser motile like a little pop up and allows you to upload one on the fly. So let's add an image chooser panel. And at this point, if I save this, my terminal is going to complain so hard that I'm missing imports. There it is. Paid shoes or panels not defined is going to say the same thing about image Chooser panel. Let's go back up to the top. Let's add paid shoes or panel. We know from a couple lessons Go that the Wag Tail Image Chooser panel doesn't come from the admin at Anders. It comes from the image at, and there's import image chooser panel, and that's just so that all your image stuff stays the same images, air sort of a core pillar behind Wag Tail. Let's go ahead and say that Refresh our terminal. Oh, no module named Wait till image. It's because that's a typo, its images. That's plural. And there we go. Now, when I refresh this page, when I want to add a new service page, you can see it up here. I now have a title, a description in an internal page. I can select another page, and you can actually traverse all the pages in your website. This way we can add an external page. So maybe I want this to go to space x dot com But in text, check this out service image. I'm going to use one I've already uploaded before, called Rocket Image. And what is the service going to be called? Adds a cleaning service. I guess we'll clean. The Rockets will make sure it's nice and shiny and white and smooth, frictionless and all that good stuff and then a description. In here. This is now a text field, so we have multiple lines. We can see that here, would you do to do. We can make multiple lines to. That's pretty nice. I'm just as, um, warm ipsum text in there. Let's go ahead, publishes Page and View Live, and we're going to see the same thing. These templates don't exist yet. Now what this is doing is looking for a service page, and it's actually getting the name automatically. So if we go down here, we can see that's a service page. Wait till Njenga were smart enough to know that this isn't the services app. So it's going to look in a folder called Services and the service page is going to change us into snake case, So it's gonna go service underscore page dot html. That's the default template is going to look for now. If we go back to our error, we can actually see if I can make this larger template does not exist. We've got a nice CRL. So I were website dot com slash services slash cleaning, and we can actually see where it's trying to get this. This template from so first place it looks for is in our Rocketman project in templates in services and then service page. So let's close up all of these. We go into a rocket man, we've got our templates in here and It's looking for a folder called Services and a file called service page dot html. So that's the first Place is looking for the second places, is going to look in the home app because we have an app called home. So it's going to look in there and then is going to look in the search app it's going to look for. I use home as an example. Home has a folder called Templates and then a sub folder called Home and then a Home Page in there. So it's really looking for the same thing. Only this time it's looking for templates, slash services folder and then service page dot html. So instead of home, it's going to be services and then service paid shot. HTML. We don't want that in there because that's really adding for merging two AP together. What we're eventually going to end up doing is we're going to create a new template in here like this. Actually, let's do it now. Services service page dot html. So I've just split my page here. I've got Service Page that each month in the left, I've got home page dot html on the right and again that service pay just lived under templates, services and in service page. That's exactly what Wag Tail and Jangle are expecting. Now we need to copy over a few things we say, Hey, we want to use our A typical based on HTML file, just the generic one. Let's add a new block in here and let's call it content much like our banner. But we just want to add some content in there and in our content. Let's do hello from service page dot html. Let's go back to our page where we're getting this air. Zoom back out. Refresh hello from service page dot html. Now, in the next couple of lessons, we're going to work with this and also the service listing page, which currently is giving you an error. We'll work with both of these and make these pretty nice looking, and we'll get more into the template management side of things 10. Service Page: before we continue adding features and and data fields to our service pages, let's actually make these pages look somewhat respectable. This is the fun part behind but development, and it's pretty crucial to have a nice looking website. So instead of having this page that just says hello from service page dot html, let's go ahead and actually update that. So it looks like a proper service, Pedro. It at least it starts to take some sort of form. After that, we are going to update our service listing page. So I've got the service page open here. How we're going to add a service listing page template, and we're gonna make that look a little nicer as well. So first things first. We need to know what we're working with. While we're working with a service page here. We've got a service page. We got description, internal page, external page, but in text, we've got service image and some panels. Let's go over to our terminal and actually make sure that this is up and running. So let's source our environment. So I'm in a source, the rocket man, then been activate. And there we are. We're inside of it. And then just simply Python managed up. I run server on Port 8000 and when I come back to my ad men, we can see that this works. I can refresh my page and it works. It doesn't give me an error saying that it's not up and running. So let's open up our service page here and let's get the title and description just as an example. So in here could make that bigger as well. In here, we can say hello from page dot title and let's throw a description in here as well. Page dot description. And just as a quick refresher, I'm getting description from the model field, the text field that we created in the last video. So I've saved this file and let's refresh it and we can see say hello from cleaning. That's the service type and a bunch of warm ipsum text that fills. That's exactly what we were putting into our page title is Cleaning and Laura MIPs, um, text for the description. All right, so I just put some front and stuff in here, some html, a little bit of CSS, and it just really makes the page, you know, start to look like an actual page. So if I go back to my browser and hit Refresh, this is what we're going to see. We'll see something along these lines but have an image on the rights, the page title, some description and a button, and that, but needs to go somewhere. So let's go ahead and feel this out with the fields that we've already added. We know we've got title description, internal page and external page Ah, but in text and a service image. So we're going to be working with multiple different things here. So let's just start at the top. We have a page title in here. We want to use the actual page title. So what I'm going to do is just use curly braces, double curly braces. He's title. And for the description, I'm going to do the same thing with the description. So page dot description and these just get wrapped around with curly braces or the mustache sort of syntax. Let's go ahead and save that and refresh our page. We can see this is starting to come together already. Let's go ahead and work with this button texts just a little bit. So we know in our models we have an internal page and external page and some button text. But in Texas, the quickest one to pick off here. So let's go ahead and change that button text. So page dot button text. Refresh your page. It says, Check this out. Is that what it says when we're editing Are paid? Yep. Sure enough, it says, check this out. Okay, Now we need to determine if an internal page or an external page has been given and create a euro. So we're going to take this in a little bit of ah longer approach here. I'm gonna throw some horizontal rules in here just so we can see text in here. And this is where the girls are going to come out just as an example. So first things first, we have that internal page. That's the field name. It's called Internal Page. Now, this is a foreign key to a wag tail page, which in the database, really just means this a stored as a number, the number four, the number five, number 10 whatever that page number is is in the background in the database. That's what it gets stored as but jangling wag tail. Well, they're smart enough to know that Oh, this is a foreign key to a Wag Tail Corp age. So a standard page, basically what this is made out of so I can select any other page. We know that it's a foreign key toe another page, and it creates the object for us. So now all we have to dio is type page dot internal page, and let's ah, let's just grab the title. We can grab the title of the page that we're linking Teoh, which is services. So it should just say services. And sure enough, it just says services. Let's go ahead and grab that. Paige, you were all instead of the title. What's grab that you, Earl? And when we refresh your page, there it is. It gives us the relative. Your l slash services is the page where we could see all of our services, and that does actually match up in the U. R L Bar. The services is the U service listing page and the cleaning page. One we're currently on is our detail page, and that is how we nested it in the Wag Tail. Edmund. So we know that that one works. What about the external page? Well, the external page is just text. It can't be anything else. We told it to just save it in the database as text. That's what a u R L Field is. It's just text. So if we add in another horizontal rule here and we look at page dot external page, let's go ahead. Refresh this, he says. Space x dot com So now we need to determine if one of these has filled Use it if the other one's field use it, and if the other one, or if neither of them rather are used, don't show the button at all. It's now we have some logic to work with. We know that these were going to work. So for this link, we need to say if page dot internal page or page dot external page. If that exists, then the button will show up. We're just wrapping this in an if and an end. If statement. So it's just logic inside of our template. It's a basic if statement now with this girl we need to determine are we going to use or look for, and then use the external page first or the internal page. I like using the internal page first so we can do if page dot internal page if that exists , which we know either internal or external page exists from this. If statement. If that exists, let's add curly braces and you're in dupage dot internal page dot you Earl and that matches this one up here online. 12. Let's go ahead and create an LF Statement elliff page dot external page. Well, type in curly braces page dot external page. We know that's just text, so we don't need do like dot u R L. Because it's not a python object, not a class, so we could do an else statement in here. We don't actually need to. This will never trigger because this will only ever show up if there's an internal page or an external page. But we could do an else statement and just give it an empty link. It's Go ahead, clean that up, refresh our page, and when I hover over this, you can see at the bottom left there, it says Local host Port 8000 slash services. Now let's work with this image a little bit. We have something to do here. Go back to our models. We have a service image. It's also a foreign key, so we know that foreign key is going to be like the number one or the number 90 or the number of 42. It's going to be something along those lines. Both Django and Wag Tail are smart enough to say, Oh, that's what's going to be a number. But I know that's going to map to the wag tail images, app and the image class. So even though it's a number when you try to use it, I'm going to look up the image with an I D. Of whatever that idea is stored. So I D four is going to look up image with an idea for and it gives us that object. So I'm going to make this just eight touch smaller and before we work with images inside of wait a windy to load the images tag. So we do load wag tail images tags, and that just allows us to use this template tag called Image. Now let's go down to where we have this image in here. It's wrapped in a picture, so we can. It doesn't have to be right above it. It could be really anywhere within this content. Block this content blocking here, we're going to say image page dot What is that image called? It's called a service image page out service image. So as its first parameter, we're going to give it the entire image object. Then we're going to say Phil 5 73 by three, 69 and then we're going to assign this as a variable as let's just call it image. I know at this point time we have access to image we could do image that you are. L whatever this u r l is going to be after it's cropped, it will create a new image for us image dot title image dot ault, which is usually the title, I believe, and let's go ahead and refresh us and we can see here. We've got our first image. That's the girl. We've got the name in here and we don't have anything for Ault. I don't believe, actually extend corrected. I believe that's the title. Let's go ahead and test that out. Let's do title. Get rid of that. Yeah, So there we go. We've got image. You are on image cult. So let's go ahead and add an altar in here. Ault is equal to image, not a lt alternative text. And where we have our image source, we're just going to replace that with the image. You are l image dot u r l And there we go. We have a service detail page. Now we don't have a header or footer or anything to actually navigate around the site, but we have a service detail page now. One last thing we can do is we can tell this to use a particular template so we can say, Hey, widow, we know this is a page use a certain template. So let's just bear with me. I'm going to break this, actually, So it's you services slash service page 40404044 because this page does not exist. Save that. And I'm just gonna wait for my server to reload. When I refresh this page, you're going to see that you get a template does not exist error. That's because we're telling it to actually look for services slash service page for a world for overall for four html and that doesn't exist. It's even telling you exactly where to look for it. And it doesn't exist in anywhere that it knows where to look for now I'm going to specify which template to use. Now, this is not going to change anything because this would have been the default. Anyways, this is in the services AP and the page. If you were to take this this class name with a capital s and a capital P and turn it into snake case, it looks like this. And then Django just assumes dot html So this actually is not necessary. But I like specifying a template this way when you're working with code with anybody else and you see a page and you go, Oh, where is that temple? You don't have to look for you just know you can highlight that and you can look for it right in your editor. I just copied and pasted and automatically found it for me, for the problem now is if we go back to our services page. That's if we click this button. Oh, no, service listing page also does not exist. And there are some other stuff we want to do in this page as well, such as automatically show our services. So in the next video, we're going to tackle that. 11. Service Listing Page: all right. At this point in time, you are loading up your website and you're trying to show your service listing page and inner admit that is this page not the cleaning one, the one that just said services services and got a subtitle. And currently there is no template for this. So in this video, let's make that template exists. And then let's go over adding a little bit of context to this. This page is Well, so I have service page opened, and what I'm going to do is just close this actually duplicate this service listing page and, ah, you know, just get rid of this stuff in here, and I've got a quick little template there. Now this page is going to live inside of these services folder inside of the templates a folder, and it's called Service Listing Page because service listing page again. If you take all of those letters, make them lower case. And then the second uppercase letter there used to be lower case. This is what it's looking for. Service listing page dot html and in fact, we can. We can specify that as well, just like we did with our service page. We can come up here and say, Hey, use the service listing page dot html Now when we refresh our page up here, we see absolutely nothing in here. That's because our template has nothing in it. I deleted everything, and it's just showing an empty content block. Okay, so I just pasted in just some HTML structure. You didn't need to watch me type all that out. But the important part is we are going to take this page title and the subtitle and replace it. So let's see what this looks like. Right now we have a page titled We Have a Subtitle and in our listing page it's actually called subtitle, and we know that every way. Tell Paige if I go to this definition here has a field called Title. So what? We can access that as well. So we've got a title in here. We want page dot title, and for our subtitle We Want to Do Curly Brace Curly brace page dot subtitle. Let's go ahead and refresh your page and let's take a look. There we go, says services. Here are the services we can perform on your rocket now. One thing to keep in mind is this subtitle is allowed to be blank. That means when you're editing this page, it does not actually have to be there at all. It's completely optional. And so in our template, we are saying that even if there is nothing in here, still have this paragraph take. So if there is no subtitle, this is what is going to render out to our go to our browser. We don't necessarily want that not just going to be an empty P tag, and that's going to cause some spacing issues. So let's go ahead and throw an if statement in here. If page dot subtitle and let's in dense that just for beauty sake. And if and when we refresh your page, you'll see that nothing really changes. But when we view our source here, if there was no text in here, this entire element would not show up at all. That's all we're doing with that now. The next thing we want to do is add some cards in here, so basically, we want to take if we go back here. So we've got a services page, and underneath this page we have all these different service pages. We've actually got the one. I'll create a few more of these in just a few minutes. What we want to do is take this child page and display it on the service page. It was two ways we can do this. We can do this through the template itself, or we can do it through a page method called Get Context. And that's the way we're going to do it here. We're going to do it. The ultra leg Tell way, All right, so again, I just pasted in some HTML in here. This is just for the most part, it's standard bootstrap with a couple extra styles that I've added. That's all in the front and assets. If you don't have the project assets, you can always download them for this course. They're called rocketman assets dot zip. And so if I go back to my page and I just refresh this, we're going to see standard card. So we've got an image, a card title of description and a link, and what I want to do here is before anything, I really just want to see how this is going to look with multiple items in here. So let's do four I in 1234 And what this is going to do is loop over each number. So this is going to give me and for So this is going to give me four cards and again that just for every eye in string 1234 And when I refresh gives me four cards in here and those are actually all on their own Roe and that is not what I wanted. I actually wanted all of these to be in their own bootstrap row and allow them to wrap themselves how that looks a lot better. There we go. So now we've got four cards in here, so we want to basically do this. So if there's ah cleaning service in here, if there's another service in here to other services, we want to automatically fill these with an image from the service page. That's a service detail Page one we just worked on in the last video with its title, its description and it's Link and naturally, its image. So what we're going to do is we are frank, a second clothes that didn't need that in her service listing page, We are going to overwrite a method called Get Contact so it's def get underscore context. It's gonna take self request all of its regular positional arguments, and it's keyword arguments. And in here, what we need to do is say, Hey, get the regular context of our page. So the context is, Ah, you know where we see due to do to do Let's go back here we see Curly Braces page dot title page, not subtitle. That's all in the context of our template. And so what we're going to do is add a little bit more to it, something that doesn't automatically come just because we have a field name. We want to create some additional stuff, so we're going to use this method, and this context is equal to is going to run a super class super function, rather get context and we're gonna pass in our eggs choir eggs and the request and return context. And what this is going to dio when we refresh your page is absolutely nothing we can see now that it's refreshed. Nothing's changed, but let's go ahead and do a little example here. Let's do context. Hello and this is going to be hello world. And now I'm going to have access to a variable in my template called Hello all capital Letters, cause that's how I spelt it in here. And if I just do this a g l l o. If I save that and go refresh my page Hello world. It shows up as completely custom context, and we can throw anything we want in there. Now I'm gonna get rid of that one, because that's not actually a useful example. It's just an example of how we can basically put anything we want into our context. So is a quick rundown. We basically said, Hey, at page comes with a method called Get Context. We acknowledge that we're going to basically write the exact same function, exact same method we're going to call the old stuff. This is the stuff that comes with Paige. That's what Super does, Super says. Basically, call Paige, don't get context. We're going to pass in all over the arguments that we normally would, and that's going to be the standard context. That's that's what the page will have by default. Then we said, Hey, add a little extra in there. I didn't spell world right, either. That's hilarious on then. Once we've added that in there, just returned the context. And that's how we had a variable to our template. That's the Django way of doing it. And because it's the drink away. It's also the Wag Tail way. So now what we can do is we can say context services is equal to, and then we just have to get all of our service pages now way to get all of our service pages. As we've got a class here called Service Page and Django, we can use their O. R M their object relational manager, service page dot objects, and we could do all and that's going to get all of the pages. But that's has no what we want because our Wang tell pages have a status, they can be a draft. They could be live, they can be public, they could be private, so we want to make sure we're using dot live and dot public, and what this will do is it will grab every single page that's currently live and currently public and throw it in a variable that we can loop over called services. So let's do this. Let's get rid of this four loop in here. Well, not really Get rid of it. Let's just do this four I in services and we will step by step will incrementally make this actually work. Now when I refresh the page because I only have one service page in here, this is the service page here. Just because I've only got the one. This will only show up with one card just like that. Now what happens if I duplicate this page? That's copy this page and let's give it a shining. That's what this new service page is going to be. Do I need to change anything else in here? Let's go ahead and let's change his image and I'm going to upload a new one, okay? And let's just publish that page. Now, when I refresh this page, I'm going to have two cards in here. The reason I have to is because there are two of these pages. And if I said hey, actually, let's unpublished the cleaning one. Yes, I want to unpublished the cleaning one. Now. Wait Till says, Yeah, okay. I understand that there are, in fact, some of these pages. But only one of them is technically live, so only show one of them. And that's why we see the one card here. Now I'm just going to republish it's page, so we have both cards. And now let's go ahead and change each piece of this. So in our editor, I'm going to say, for each card in service ISMs in a call it card. The simplest thing I can change here is the title card dot title, and if we go back to our models in here Models, models, models, we're grabbing all of the service pages. So we now have access to every single field that's available in a service page. We now have access to the description, so let's grab this description as well and replace this one card dot description. Now where am I getting card from? Well, because services is, it's a list of basically the service page. We're going to loop through each one card because we're going to create cards on the front end. And so, for each one of these pages, one of these cards we have access to the title and the description just like a regular page . So let's go ahead and save this and give us a refresh and we can see that we've got cleaning shining. We've got some alarm ipsum in there, and now we need to change the URL and the image. All right, so we let's let's do the image first. I'm just going to be the biggest one is going to the funnest one. We need to make sure we have wag tail images, tags loaded, which we currently dio, and let's go ahead and create a new image in here. We're going to say image card. And then what is that field called? It's called Service Image. Going to give it a service image, we're going to say Fill it with 5 73 by 369 as card image. And then, just like in our last video, we can use card image dot u R l. We can also give it an altar, which is always a good idea, even if it's empty. Guard image dot dot a lt for the word cult. And look at that. This is starting to come together now when we click this, this isn't actually going to go anywhere and you can see at the bottom left of my screen. It says at to do So let's let's fix that. Now we know that every card has a page. Well, because that's all this is this is a page object. We just renamed it to card for this loop. So we're going to do for each card, generate a card. You are l We're going to put the girl in here as well, and we're also going to put it in here. So basically, when anyone clicks anywhere in this card text image or the link is going to bring them to the proper page. So here we go. I refresh the page. I click Cleaning brings me to the cleaning page. I click back, Click Shining brings me to the shining page and there we go. Just like that, we have a services listing page, and for every new page that you add, it will automatically show up in here. It will just keep adding them. That was a super quick summary. Here's what we did in this video. We added the page title page subtitle We check to see if the page subtitle even exists. Actually, there's one other thing we should dio is. Is there a description? Eso. Then we said, Hey, we're going to add some custom stuff into the context of our template. So then we added, I get context method. We said, Hey, grab the regular stuff that wait till always give us and then add some extra stuff in your This is using Django Zo R M So we're grabbing all the service pages were making sure that all the pages air live and public, and we're simply returning them to the context. Then, in our template, where the context is being injected into, we can loop through every service. We called it card. So for every iteration we call it card, we created an image, give it a title or displayed the title, displayed the description and gave it a your l. It was one last thing I did not do is I forgot that the service page description is, in fact, optional. So we've got service page description. Blank is equal to true. It is an optional field, so we're going to say if card that description and simply end if now it's not going to change the display because they have a description. But if there wasn't one, there's not going to be an empty paragraph tag and her document object model in her page in her html. And that is just one of the ways that we can create a listing page and list all the child pages underneath it, just like we've done here. There are a few other ways that we can do it, but this is by far the simplest. 12. Adding Page Validation: up into this point, we have been adding a couple of fields into our service pages called External Page and Internal Page. And what we're basically saying on the template is, if there's an internal page, use that you, earl, or if there's an external page, use this text and now is a good time to add some page validation. So if both of these air filled out, we're going to add a little error that says, Hey, only one of these can be filled out at any given time. So I'm going to open up my editor and in my service models dot pie file. And if I closes up, we can actually see where this is services and then models dot pie. We're going to add another method in here called Clean. We're gonna say, Define clean that's going to take self super clean, and that's just going to do its regular cleaning. So whenever you add content to the page just going to escape your content so we can, you know safely be entered into your database and you don't have to worry about SQL injections and things like that. Now what we're going to do here is say, if something cause and error, that's really all we're doing at this. So if you just keep this in mind these two lines in mind, this is actually quite easy. So we can say if Self dot internal page and self dot external page both fields are filled out, and that's actually know what we want. We don't want both fields to be filled out. We want to just one of these to be filled out, and this helps guide your content entry people to doing basically the right things. I don't fill it out in both places because that could be confusing. And if anyone else were to come onto your page and say, Hey, there's a link here, nothing here which which one's going to be used? I I don't know, but this is going to do is throw you an error and say, Hey, only one of these could be used at any given time. So now let's go ahead and add an error So we're going to raise an exceptional raise and error here, so this is pretty much all jangle at this point. We're going to raise a validation error and we're going to give this a dictionary, and the 1st 1 is going to be whatever your field name is. So what this is saying is, if there is, in fact an internal page and an external page, that's this if statement here, we want to throw an air onto one of those or both of them. So I'm gonna throw on air on the internal page field first. So that's going to be the key for the first dictionary value here. And then again, I'm going to give. It's just one more validation area. It seems a little weird that we're nesting that. But that's all we're doing here. And we can say at this point please only select a page four, Enter an external, you are l. So now that's all fine and dandy. But if you actually go to save this page, you're gonna run into a problem. We need to import validation air. We have not used this yet, and so we have not imported it yet. So let's go back to the top of our page here. Now let's do from Django decor dot Exceptions, import validation. Error. That's all we're doing. I'm gonna slide back down here clean that up a little bit. Save Django decor dot Exception does not exist. That's because that was a tape or lack of typo. It's Django. D'accord out exceptions. There we go. Now let's go ahead and try to save this page. What happens when I even just try to save a draft? This page could not be saved due to validation errors that's looking good. It's looking good. And here's the exact text that I wrote. Please only select a page or enter an external euro. No, there's no error here, but we can add one very, very easily. So let's go ahead. Scroll back down here and I'll make this just a little smaller. Size fits on one line That's far too small, and I'm isn't a copy and paste this because all we need to dio is changes field name, and I suppose we should have a comma in there because this is a dictionary. So this is the first key with the first air message, and this is the second key with the second error message, and this text, by no means need to needs to be the exact same. So let's just do that that that that that that that the dead I'm gonna save that and you'll see that the first air is gonna say something very polite. And the second error is going to have a bunch of disease. There's ads in it and is going to say the exact same text afterwards. Let's go ahead and save draft. The page cannot be saved. You do validation airs and please select a page or enter an external you well, and in the 2nd 1 down here, we've got said that that that that that that that dead please only select a page or enter an external you Earl. So now we've got two errors for basic validation. So actually gonna clean that up because I don't like all those heads in there. Doesn't look very nice. He even throw this into a variable to really clean that up. And now, at this point, all we have to do is, let's say, get let's get rid of Space X. We don't want to necessarily linked to Space X. On which page are we on the cleaning page? So let's go ahead and let's just publish this now and it works. There's no more validation errors. It actually allows us to save the page, which is really, really good news, because up into this point we can add an internal and an external you Earl. Now let's say every page needs to have one or the other says Go ahead, clear this choice. There's no internal page and there's no external page and it saved perfectly fine. What we could say now is, I'll just make that slightly bigger again, if not self died internal page. So if there's no internal page and not self external page, so there's no internal page and there's no external page, we can also raise an air in here. So let's go ahead and raise a validation error. This is going to be a dictionary. And again, it just looks like this. We've got a key with validation error and then a second key with another validation air. That's really all this. Looks like we want to put those keys as the internal and external pages and the validation air is going to be. You must always select a page or enter an external you Earl and I'm isn't a copy that cause I like that wording and That's good enough for both those fields that I'm working with. So I'm gonna save that. And now when we save our pages looking for two things, if both of these air filled, then it's going to cause an error. It's gonna throw in air some validation air. And if neither of these air filled out, it's also going to throw validation air. So let's go ahead and saver draft. Hey, look at that. The page could not be saved due to validation airs. You must always select a page or enter an external You Earl says the same thing down here. Texas Nice and read your text field itself is nice and red, so people content entry people know exactly what went wrong. Now I'm going to choose any page. Ah, and I am in fact going to choose the home page published, and it publishes perfectly fine and so ultimately, this is all we do to add a validation error for our page fields. So we've got a page up here called Service Page, and we want to make sure that there's always an internal or an external page set, and there's only ever one of them being set at any given time. 13. Adding a Header and Footer: if we take a look at our page right now, it looks empty. We've gotta services, Page, We've got service detail pages and we've got a home page, but we have no way to actually get around. The home page has a nice banner. That's cool, but there's no header or footer. So how are we going to get around? Well, let's go ahead and add a global header and a global footer to our Wag Tail website. So first things first. I'm gonna close that and open up base dot html and that lives inside of our Rocket Man folder and inside of the sub folder called Templates. Now in here, I am simply going to say, include a file includes header dot html and that's all I'm going to dio and below all of my content, I'm simply going to include a footer dot html. Now this is going to look for template files. This is a template file. We're working with the base one, and it's going to look inside of our template folder that's right here for another folder called Includes and then a file called Header. And then it's also going to look inside the includes folder once more for a file called Footer Now if we save us and we refresh our page, yeah, template does not exist. We've seen this before, and this actually tells you it's looking for a template. So in the templates folder or in one of these template folders for a some folder called Includes and a file called Header dot html. So let's go ahead. Creating new filing here includes header dot html. So I just created a folder and a file, and I'm also going to go ahead and create footer dot html and these were totally empty files. Nothing in there. So at this point, we have effectively done nothing to our website. So if we open up header dot html, we can had basically anything. So let's do this. Div. This is ah, header Cool. That's gonna show up on every single page. So if I go to slash services, this is a header. Shows up there, go to a service detail page, it still shows up. It's always there, So let's make this actually look nice. Let's make this actually look like a proper navigation. All right? So all I did here was I basically just took a navigation from get bootstrap dot com and I injected it in here. I did one little thing. I added one link. It's not even a real link, and I gave us a relative linked to our logo. That's it. So when I go and refresh your page, we're going to see we've actually got Rocketman logo and a single link, and that's going to show up again on every single page. So as I hit the back button, refresh that one. There we go. I'm on the services page. I can go to any of the detail pages, and that header is always there. Now there's two things we want to do here. First, we want to not link directly to Static. You might see this in other wait till projects you might even see this. Another Django projects where people just link directly to the static folder, and for a lot of purposes, that's actually OK. But for some websites, you might not want your static folder to be called static. You might want it to be something different. I don't know why you would change it, but some people just want it changed so What we're going to do is use the static template tag static, and we're going to simply give this a string and say, Hey, wherever static is whether we change that now or if we change it in the future, wherever that folder is jangles gonna figure out where that is the images sub folder inside the static folder again, we don't know where that is, so it's very relative at this point and the file logo dot PNG Now, to make this work, we actually have a load this in here. So we're going to load the static tag. And when we refresh your page, you see nothing changes. And even if we look at the source in here, it looks the exact same slash static slash images slash logo dot PNG Looks like nothing changed, but in production, for example, if you gotta wait till dot io and we right click and inspect here, you can say yes. This is using static as well. They could have used any other folder. They've got a sub folder called Image, but then they've got fingerprinting turned on. So every time they collect static every time they had more assets to the website. You're going to get a random hash and effectively what that does. Is it cash busts, all of your assets, all of your CSS, all of you, JavaScript, all of your images. So that's if you're using Amazon s three or cloudfront or digital ocean spaces or anything that might have cashing it immediately gets busted. And that is why typically, we like using static. So it's got to benefits, Really? The other thing I did was I added a Lincoln here. So if I go back to our page, I just have one link, and what I'm going to do here is I'm actually gonna do another loop. So for I in 123 I just want three of these items. And so I just did some in denting and added the end of four tag in there. Let's go ahead. Ad I as well. This is going to be our menu in the future. So for every eye, for every integer in 123 show link one link to link three. And just like that, we've got Link one linked to link three. Please don't go anywhere. We could hard code these if we wanted to, but we don't actually know if our future client or future Selves are going to maintain those links. So, for instance, I could say Link one always goes to slash services. But what if one day I go to my services page and say, I don't want it to be called services? I wanted to be called rocket maintenance services. And maybe that's just better for S e o. Well, all of a sudden, my link that goes to slash services, this page is going to break, so we don't want to do that either. I'm not actually going to say that. I'm gonna keep that add services and that's how we add a header. And next, I'm going to add a footer. So if we go back to our base, we've got includes Footer. But our footer page currently has nothing in it. Okay, so again, I just pasted Ah, template, footer in here. It's a bunch of hte email and whatnot. There's actually nothing really going on here, except there's a for loop toe create fake links again, just like we did in the header and ah yeah, that's about it. So let's go ahead and take a look at this just as a quick preview. So I'm gonna get rid of that scroll on down, and I just refreshed my page and there we go. We've got a global see ta Uh, some text in there, a button that's gonna eventually go somewhere. I guess social media icons hours to do is links 12 and three. And so these are all features that we're going to be adding in a little bit later. But for right now, it's just nice to know that we are starting to actually create a website that looks like something actually looks like a website. At least I can get to the home page from really any page at this point and in future lessons, we're going to add a proper header in here. Ah, proper navigation. So we're going to create menu items. We're gonna create three of them in there. We're going to then copy, though, so they also loop through the links and create the exact same links down here. We're going to add global website settings for the contact us for the hours for social media settings for the links. We're going to make sure this is customizable. This is customizable, invisible. And this page this to do button actually links to a proper page. So we're going to get to all of that down the road. But for now, we're just going to keep it here as placeholder. 14. Adding Debug Tools: okay, Our website is starting to get complex enough that we should probably start considering some debugging tools, something that's going to help us along the way to figure things out. Figure out if there's a problem which we haven't actually really run into anything, but it's gonna help us along the way. So we're going to install two things. The 1st 1 we're going to install is going to be create new file here is going to be called Django Debug Tool are, and the 2nd 1 is going to be called P You d Be. And that is a python interactive de bugger. So it's a little better than the regular D bugger. It's a tool. I like both of these air totally optional. If you have better debugging tools that you prefer to use, definitely use those. But these air to that. I absolutely love that. I pretty much put on every single project just because it helps me out tremendously. So let's go ahead, open up our terminal and cancel our server. And while we're inside of our environment, so whichever virtual environment you're using, whether it's Pippen ven vor v envy docker, vagrant, anything like that. Just make sure you're inside of your environment and type Pip. Install Django Debug toolbar and you can see that successfully installed Jingo Debug toolbar. Version two point once lets you pip show Django debug to a bar, and that's going to give us the actual versions. We've got a name here. That's our name. And we also have a version which is 2.1 so cool We have that installed in a virtual environment. Now the first thing we want to do is we want to open up requirements dot txt Sure, whatever is in there, let's go ahead and create a new file offer this one called dev dot txt and deleted. The reason we did that is because I want dev dot txt and requirements dot txt toe live beside each other in our project directory so you can see I've got requirements out txt in the same folder. I've got dev dot txt Now what we're going to say inner dev dot txt is require the requirements dot txt file and below it, we can put Django debug Toolbar is equal to equal to version 2.1. So now when you spend up a new project. Instead of doing pip install, dash our requirements like what we usually do for a new project. You can do Pip Install Dash are dev dot txt and it will install of your base requirements, but also the Dev requirements, such as debug to a bar or P. You d be No, but installs it into our virtual environment. But that's not actually installed into our project. So let's go ahead and close that and that and let's open up dev dot pie. So these are our development settings. We can add our own development settings and here, or we can overwrite them with whatever local settings we want in a local dot pie file. I'm just going to throw them in here because when I commit this, I want other people to have these exact same changes. Often we don't really commit local DUP high files or local files at all just because it's a little extra tweaking that you need to do to get the project running on your computer. Whereas I want everybody to have the so I'm gonna put this in the dev dot pie file now. First thing I want to do is I want to add this to my installed APs. Let's do installed APS and this is coming from base dot pie. So if we open up based on pie online 26 all the way down to Line 52 we have our installed APs. So we're basically saying, Hey, Django, use the base settings That's cool. And also grab your installed APs and add some Maurin there. So let's go ahead and do Plus is equal. And let's throw a list of items in here, and this one list is going to be debug toolbar. And at this point time, we can rerun her server if we want to see if anything comes up any sort of errors. Look at that. And it says, Hey, Django, debug toolbar Middleware is missing. We should add that to our middleware. Okay, let's go ahead and add that to her middle where now all of these could be found on the jangle debug toolbar. Read me instructions. So if you go to, I think it's ah, read the docks or Django debug toolbar dot Read the docks. A quick Google will will tell you exactly where that is. It'll tell you exactly how to install this. This is really just I'm doing most of this from memory here, So let's do middleware. And again, let's add to our middleware, and we're going to give this a list of that debug toolbar middleware. What was that complaining about this one right in here. We want to select all of that and paste it in there. That's what is complaining about, and lastly, this is a weird one. But Django Debug Toolbar requires us to give some sort of internal I p that is allowed to use. And for this we just want to use 127.0 dot 0.1. That is your local host Save. Let's go check out our terminal. Everything looks OK in terminal land. And let's refresh our page here and at this point time you actually can't tell, but it's trying to work. We have one more step to do here, but it's really trying to work. So generally we're going to see a little Django icon up here, and that's going to be our profiling tool. So the last thing we need to do is open up our you or else dot pie and that just lives under Rocket Man. Here we've got a Rocket Man folder, right beside setting static and templates. We've got a file called you URLs dot pie and in here we we want to basically add some extra you RL's so that the jangle debug toolbar is allowed to show you what it wants to show you . So let's go in here due to do to do settings. We're going to say, if settings debug is true, that means we are developing locally. Debug is only ever true when you develop locally. Once you go into production, you should have debunked earned. Often you shove Engine X or some other sort of application serving your static files. But for now, this is okay. Let's import debug toolbar, and we don't need to do anything with these. And let's just go ahead and add debug toolbar to rul patterns. So again, this is only going to show up for our local development. So you are all patterns, is equal to your old patterns, and let's let's upend it a different way. Let's add Uriel patterns at the end. You are L patterns is equal to whatever the list is plus existing neural patterns. But that path underscore. Underscore debug underscore. Underscore slash. And then let's do include debug to a bar that you or else now that's going to break because we probably don't have path or include installed or included the right way. It's probably installed to come to the Django, but it's probably just not imported the way we're expecting it to be imported. So let's go back up to the top. Do we have Django you or else we've got confident you are else, but we want from django dot urals import Include in past. Okay, let's go back and refresh our page and it's still not showing up, so I obviously did something wrong. Go back to dev dot pie installed APS middleware installed. I p is No, that was supposed to be install. Nope. Internal eye peas. Let's go ahead and give that a refresh and has ah, there it is. So it Ah, I guess a little finicky. I mean, there's a few steps, but again, that's all documented either in this video or in the jungle toolbar Read the Ducks website . So in here we've got jangle debug toolbar. It tells us the version that we're using the processing time shows us or settings. How many SQL queries. This is a big one because you're working with a content management system. You can end up bloating this to be a lot, ideally, shoot for less than 100 queries if you can. If you ever see a website that has 1000 queries, something definitely needs to change. That is, that's terrible. How many static files are we using and actually go back to the SQL queries? We can see which ones have been run in here. That's pretty cool Weekend Say, Hey, explain yourself and it does. It tells us what's going on. That's pretty neat so we can see our static files. We can also see our templates in here. We've rendered 13 templates, so we've better service listing page. We know where that's coming from. This is really helpful. When you started an existing project and you're like, Oh, where is this template? Where are these templates coming from? Don't know where they're coming from. This will tell you exactly what's coming from. This comes from services slash service listing page. That's pretty cool. We know it's using based on HTML. It's got a bunch of Wag Tail stuff in here. So it's using the user bar more weight telling men stuff, and we've got an include for Header and include For Our Footer was telling us all sorts of things, even tells us what's going on with cashing and signals and logging and all that good stuff . So this is a full profiling tool. The biggest helpers in here are your SQL queries and your templates. If you have a site that's loading really, really slow or you don't know where a template is coming from or where some data is coming from in a particular template, this is going to help you out tremendously. So this is to a number one tool. Number two was called P You d be. So let's go back here. This one is actually super easy to install, so we do Pip install P you d be. Let's go ahead and do pip show p you d Be and it's using 2019 dot to let's go ahead and restart our server with Python Men Shop. I run server 0.0 dot 0.0 cool and 8000 and we could close up all of those. And let's open up dev dot txt again. And let's put P D. B is equal to equal to 2019 dot too. So we're version locking the version of PDB that we're using in our project in just the Dev requirements. And for this one, that is literally all we need to do. So let's give this one a shot. Let's go to service models and to activate this one, this is super easy. You just throw it wherever you are, having some troubles when you need. You need to debug something and you just type import you d be colon and then p u 0.2 db braces. And that will run your d bugger for you. So what? This is going to you? Is this going to tell me everything that I have access to in this particular view? All the variables, all the data, everything. Let's go ahead and create a new variable in here. Just as an example, it's called Space Company is equal to X. Make sure our servers running Okay, looks a okay. Now let's go back to our services page and let's just let's refresh this page. The reason why we're on this page, by the way, is because we have a service listing page and we're adding custom contact. So we've got some custom logic in here, not really logic, but some custom data that we want to throw into the template. And maybe we need to check this out and figure out what's going on. So among the service listing page and a hit refresh, and it looks like it's just going to load forever. But if you boot up your terminal again, you're going to see this extremely ugly page now. Usually there's a little pop up in the wind in the middle there, you just have to hit. I believe it's the right arrow and then enter, and that will just get you in here. And then you can hit left and right, and you can go through here. I usually just go straight to the right section. You can see the green stuff is changing in there, and so we've got access to the guards. The context, the keyword arguments, who had access to the whole PP. You d be thing we don't need that were already running. It request is running a whiskey request. Self is the service listing page. Our custom variable called Space Company, is equal to X, and that's it. We've got some other stuff in there that we're not going to get into because this is the biggest help. So let's go into our context. I'm going to hit the slash key. That's right above my return key, and that's just going to open it against not super intuitive, but it is super helpful. And in the context we have page self request and services so we can actually see that when we go back in here to service Listing page, we've used page page dot something page dot title paid shots. Subtitle And so if I open up this one again using that same key, we can see everything that's available to us in the template. And ever since, scroll down until I see the word title dot tono, Where are you? A CEO title slug all sorts of stuff. There's our subtitle here. All the services we can perform on your rocket. That's exactly right. And this is the page title is just called services. This is our page. You are l We've used that one before, and so now we can actually see everything that's in here. We've got a full ul path. Should you ever need that all sorts of stuff, self is going to be the exact same thing. Self and Paige are identical, but the one we added, maybe the one we want to debug is these services. And that's because if we go back here, we said Context services is equal to service paid shot objects that live that public. Maybe we don't know everything that's in there. We can't figure something out. It just seems like it's throwing some mystery data at us so we can go in here. We can open this one and it says zero and one, so we automatically know it's a list. It's not a dictionary. Dictionaries have named Kees. Lists have indexes or indices just for their index zero and one makes it a list. So the first page is going to show up, and this is the service page who could actually see appear. So zero that's the 1st 1 is a service page, and then we could use scroll all the way down, gets all sorts of things Paige. I d. Number five. That's neat. Is it life? Yep, that one's live. Is it lock? Nope. It's not locked, Max Count. We haven't learned about that, but we will, Oh, sorts of stuff in year and there does title. The 1st 1 is cleaning. Someone keeps going down and go to Number two your index number one. But it's the 2nd 1 in our list. Just go all the way down to where it says dot title again shining. So the first services cleaning the second service is shining, so that's telling us exactly what we want. Or in this case, I want to see just the titles, which would be cleaning and shining in that order, cleaning and shining. So that's really cool. That's another way of really just debugging what is being presented to you in a particular contact. So for us, we're in the get contacts. This is where it's being run right in here, And this in the top right is all the data that we have access to. So if you're ever wondering, what is that? A CEO title field name? You just go in here, look at the CEO title name or you could look at the wait till source code and then, lastly, to get out of here, you just hit the letter Q to quit. It will complain. Yep. It will complain. No exception. Message supplied. That's okay. And we're just going to get rid of that. Don't that one either. Scared of that server restarted itself, but if it doesn't, you can always restart it manually. Refresher Pidge. And we saw over debug toolbar and everything. So there we go. We have Django debug toolbar and P you d be installed. We've also added a new file called dev dot txt. So whenever you install this project, you're going to type pip install Dash are dev dot t x team and it will install everything else from the requirements, not txt file, but also Django debug toolbar and P you d be. I'm hoping this is gonna help you out in the future again if you have your own tools and you don't like these ones, totally disregard this. But these two tools have helped me tremendously over the years and even today I still installed him on almost every single project just so that I can have that little extra helping hand 15. Flex/Misc Page: All right, let's go ahead and create another page type. Now, this is not the most exciting thing that we've been doing so far. But we're going to be creating a regular page type. I'm gonna call it a flex page. Ah, some people call it a generic page or basic page. Some people call the miscellaneous page or a standard page, but really, the idea is that we have some sort of page. It doesn't have any real purpose except to serve content. So this page is not going to necessarily be related to ah, blawg or services. It's not like a service detail page where it actually has a particular purpose. This is just a regular content page. No, we're not going to be adding content just yet, but we do need to create this page for a future Lessons. Let's go ahead, activate your virtual environment and go ahead and run. Python managed up. I start app. I'm going to call it Flex, and then go ahead and run your server with Python managed up, I run server Port 8000 and in our code, I'm going to open up base dot pie and right underneath services I have a new app in here called Flex and you can actually see in my editor here. I've got a new folder called Flex, so I'm gonna save that and open up Flex. And I'm gonna get rid of some stuff that I know we don't need because Wag Tail takes care of a lot of this stuff for us. So views up. I we don't need We're not writing tests. We're not doing anything with our admin. We do want to keep migrations in it. APs and models, that pie and really all we're going to do is create a regular page once again. So we're going to do from wait till dot core dot models import page, and then we're going to create a new page called a Flex Page, and it's just going to inherit the page model. And for now, we can do pass, and that would be good enough to create a page. Now let's actually take this one step further. Let's do class metta, and let's give this a ver boast names. So we're going to see that we can actually change the name here, so that's due for both Name is equal to flex, and it would be normally just called Flex Page. But we're going to call it Flex Miscellaneous page, and the verbose named plural is going to be flex missing pages just like that. Now, if we open up a browser and I'm just editing the home page here, if I go to Add Child Pidge, we have a flex misc page in here. I'll just make that a tad bigger now when I click this because I haven't run migrations, I expect this to throw me the Classic 404 We ran into this air once already, and that's very much expected behaviour. So let's go back to our terminal. Cancel that, and I'm just going to clear that page off. And now I'm going to run python manage dot pie, make migrations. And then, once more python managed up, I migrate and again, what that does is when you make migrations like this one just created a file called 0001 underscore initial dot pie. And that really just writes out python IQ instructions for what Django should do to start shaping your database so that this page actually works for us and then when we run Python managed up high migrate. This will apply this new migration file, and it will actually structure to page that we're looking Teoh to create in our database. So it manages our database table on columns for us. Now let's go ahead, rerun our server, close that down and refresh. And in a sec, this will load. We can now create a new flex page. So let's just call this. I don't know about us. Just a generic page Now there's nothing else on this page it We're going to keep it that way just for a little bit. We just need this page to be up and running. So go ahead, publish this page. Things were looking well, when we click of you live. We've seen this before. We've seen this a number of times. We're going to get a template, does not exist error. And just like that template does not exist at about us. It's looking for flex slash flex underscore paid shot. HTML. We can actually go and change exactly which pages looking for. We know that if we look in here using engine Django, that right sort of in this right hand air area. We can see that it's looking for Flex Page in the Flex folder, which lives in the Templates folder. It's also looking for it in the home app and the search app and then also inside of all of our packages. Let's go ahead, make this template actually exist. Open up our code and I'll just close all of that open up, Rocket Man, go into templates and let's create a new folder in here called Flex and then a new page called Flex page dot html. And this is exactly where Wag tails looking for this template. Next, we need to actually extend this from a page. Let's extend this from based out hte email. That's where all of our HTML is going to originally come from, and we're just going to overwrite a little bit in here. Let's create a new block, and we're gonna call it content. Now if you're just joining us, if you open up based on HTML, we have a blocking here called banner content and extra Js. We're going to take this content one, and we're just going to inject a bunch of HTML right in here. So what? I'm going to do is just right. Some bootstrap code real quick. So I've got a row. I've got column medium eight, and let's offset that by two So offset MD to and I have an H one in here. I've got a custom class that I'm going to be using called CC inner title, and in here we're just going to use the page dot title page dot title and every way tell Paige comes with a field called Title, so we know we have access to that. Let's go ahead and refresh our ever page and we can see there it is. We've got a little about us title. That's exactly what I inputted into. Wag Tail is a content management system that's the title of our page. No title of our page shows up here, so that's all there is to really creating a brand new app and a page from scratch again. We're going to be adding a little bit more to this page as we start getting into stream fields a little down the road so this page could be completely customizable. So go ahead, get commit that save it, do whatever you need to do, and when you're ready, we'll head on over to that next video 16. Testimonial Objects: Up until now, we've been creating Wag Tail pages with just a few custom fields. This has largely all been just weg tale stuff. But now let's take a step sort of backwards or outwards rather. And let's work with Django. And let's enable a jingle model to be accessible inside of the wag. Tell admin. Now, basically, with the Wag Tail model Inman, we're not going to be needing the Django model Inman. Now, if your brand new d jango whenever you work with models and you create new data, it comes with the Django admin, and you can manage all of your data that way. Well, when you create a way till happy you now have to Edmunds and we're going to basically say, Don't use the Jan Goodman at all, I haven't even shown you the jangle wedemen. We're not going to take a look at it. We're simply going to use Wag tells model Edmund. So I am in my virtual environment and I had my server running, so I'm gonna cancel that and I'm going to create a brand new app. So I'm going to do python manage dot pie and we're going to create a new app called testimonials. If I could spell the right to test it mony als and then I'm going to run that server again and open up my code. Close the stuff from the last lesson and you can see in here I have a new folder called Testimonials. Now, views are really just a jangle thing. You don't need it too much when you're dealing with wag tell unless you're writing a custom jangle view. So we're gonna get rid of this file. Well, we will be writing any tests. I don't think we need to write a test for this. And we're actually going to keep our ad men APS models DuPuy In It Up High and Migrations Folder. So the biggest change here is that we're really just keeping the admin dupuy file. Let's go ahead and open models dot pie. And before we do that, let's go ahead and open base dot pie And let's actually activate this. So we have all these different APS. We have a new app in here called testimonials that matches the folder name. Where did you go? Testimonials. Right here. No, we're going to dio is save that and close Based on pie? No, We created a jingle app, and this is pretty much all we need at this point. We're going to say class and we're going to add a testimonial, and it's going to inherit models dot model. This is just straight jangle. This at this point has nothing to do with Wag Tail. And we're gonna call this a testimonial class definitely needs a better doc String. But for now, that's all we're gonna put in there now. What is a testimonial? Testimonials A quote and has a person. So we're gonna say attribution So what is that quote going to be now? This is going to look a lot like a regular way tail field. So if we go to home models dot pie, we've got lead Texas equal to a char field. We've got foreign keys. Notice how is using models that pie here? We're basically going to be doing the exact same thing. Only this time it's not a way. Tell Paige it is a jangle model. So a quote. How long do we want her quote to be? Let's give us a text field with a max length of something relatively short. 500 characters. Can this be blank? No. Can this be? No. No, this is a mandatory field and attribution were basically going to do the same thing here, but we're going to use a char field, so this one's going to have a max length of se. Ah, maybe this needs to have a name and a business name. So Caleb Tallinn from coding for everybody. So I'm just gonna guess that's roughly 50 characters. I Can this be blank? No. Can this be? No. No. And I am going to split thes onto their own lines because it's just gonna make life a little easier if we can see this all on different lines. There we go. Now we can do a few different things here. We can optionally give this a verbose name. We did this in the last lesson for boast. Name is equal to its a testimonial testimonial and the verbose named plural could be testimonials. Now, jangles already going to do this for us. Wag Tail already does this for us. We don't need to set this because, well, it's gonna take our class name, testimonial and plural to plural eyes. Something in English Basically you add Justin s And so it's saying, OK, I'll just add an s to whatever the for most name is so technically this is optional. It's not required now. If you wanted to change the name, but you wanted to keep the model name as testimonial, you could do that. This is for display purposes. Next, we want to give it a string. So death underscore. Underscore string underscore. Underscore inner str It takes one parameter self because it is in an object. It's in a class inside of Python, so it takes self. And this is just going to be the string representation of this item. And I'll show you what that looks like in a little bit the string representation of this class for this object and we're just going to return itself dot quote or attribution Let's do self don't quote. In fact, let's not do yourself a quote. Let's return an F string. So self dot quote by self dot attribution So it's going have a very long string name. Probably too long to be totally honest, but that's what we're going to work with now. Let's go back to our server. Cancel it and let's run Python, manage that pie make migrations and all this is going to do is create that classic 001 initial dot pie file. We've seen this before. That's basically just our instructions written in python on how jangles going to structure our database for us. Now we're going to actually apply those with Python managed up high migrate that works. Let's go ahead, rerun her server and let's go back to our admin. So I'm just back in my wag telling men here, and I'm just gonna load up a page and you're going to see once it loads, nothing has changed. You're not going to be able to find testimonials anywhere in here. The fact is, it does exist. We have structured our database with testimonials or with a testimonial table, which we can see in our model here. However, we don't have access to this at all. So let's go ahead and give us access to our own piece of data here so that we can add testimonials. So let's go ahead. Open up at Minda Pie. This is the first time we've opened up at min dot pie and I am actually going to get rid of all of this. This is Django Wittman, and we're going to be using Wag Tails Admin. So let's do, Ah, little import here. So let's do from Wag Tail Dodge Con Trib dot model admin dot options import. And we want to import Model Edmund and also model admin Register. Now, before we continue, you can see that this is coming from the package called Con Trib. I'm not really a package. It's more of, ah, holder for contributions for additional features that Wag Tail can come with. We need to make sure that this is enabled. So let's open up our base dot pie and let's look down here for wag tail dot con Trib dot model admin. So we do model Edmon. I don't see it anywhere in here. So we're going to want to enable this, and I'm just going to put it with the rest of the contribute Weddell dot con Trib dot model Edmund, Save that. Let's check her terminal. Everything seems to be a okay up here. Go back to our browser. Give that a refresh. Life is going to look good once I loads. There we go again. Was not going to see anything. So it's closed based on pie. Let's continue registering our application here. Next, we need to actually import the testimony itself. So because this isn't the same folder at min dot pie and models top high, we can just do from dot models import testimonial. Thank you. V s code for auto feeling that for me. Next, let's go ahead and actually register this model. Inman. Now what we need to do here is we need to create a model it men with our testimonial. That's this class here. And then we actually need to register it. So we're going to create a class a new class and admit in class and then register that. So let's call this testimonial. Admin could be called really anything and is going to inherit model admit nuts. That one right there. Thank you. Have that doctoring of testimonial at men That is a time tested and pointless doctoring. But you know doctrines, doc. String So model is equal to this is where we start to actually set our model women. So model is equal to test him memorial, and that needs to match the actual testimonial class. That's the Django model that we see in models dot pie. That's this one here. Next, let's do menu label. What do we want to call this in our wag Telemann. So when it shows up in her menu over here, what do we want to call it? I'm gonna call it testimonials. Let's give it a menu icon as well, and I'm just gonna give it a placeholder. A menu order somewhere between 101,000 issue. Let's, I don't know. Let's give it a 290. Add two settings menu and you can find all of these settings in the documentation as well at docks that wait till dot io. I don't want to add this to the Settings menu. What this is saying is that it there, Let's not added there, we don't need it there. Exclude from Explorer is equal to false. Let's not excluded from the Explorer, and there's two more in here list display, and this one's going to be equal to you. Something we're not gonna add that just yet. On this do search fields is equal to something as well. We're not gonna add that either. Let's go ahead and save this. Check our terminal. Make sure that it did actually refresh. Occasionally it doesn't. And let's go back to our wag tail page and just hit Refresh. And sure enough, you see that it still doesn't work Well, that's because just a minute ago, a couple minutes ago, I was saying that we need to create an admit model and then we need to register it. So let's take this model had been register and let's add this as a decorator. So we're gonna put that there hit save again. Make sure Terminal are Django Server restarts itself. Now let's hit refreshing. We'll see on the side here. It's going to say testimonials just like that with our little icon. That's a placeholder icon. We specified that one testimonials. We don't have a testimonial. Let's go ahead and add one. What to do or do not. There is no try classic Yoda to go ahead and save, and we have a testimonial. We can add another one in here, and this is going to be oh, from Chewbacca, and they're really so we've got testimonials in your That's pretty cool. Now, at any point in time, inner get context functions when we're when we're editing a page like I was going to reference it. But let's actually talk about it. We have a Service Models page here, The service listing page and Inter get context. We can now actually say testimonials dot objects dot all, and we can get all the testimonials. So that allows us as developers, to allow content entry people or your client to input some form of data. And we can then use it a little bit later. All right, let's uncommon that list display, and we want to set two fields here. So this is going to be how it's displayed right now. It's just the one column just this testimonial and this is the string representation. So that string that str method here says the quote by whoever said it. So if we did this a bunch of junk in here save that, wait for server to refresh, we're going to see all that junk actually shows up in her page here just like that. That is the string representation. Now, we don't want that because that looks terrible. And instead of just showing the string representation of our class, what we can say is, Hey, wait till show the quote and the attribution as columns. Let's go ahead and add quote in lower case and attribution. Go ahead, save and refresh our page. And would you look at that? We have quote an attribution and it's sort of all. Now let's add some search because maybe maybe as a rocket maintenance company, we have so many customers I can only think of, like three off the top of my head, Blue origin, Space X and NASA, maybe Virgin Galactic. So we have four customers, but yeah, they each write us 500 testimonials. So we've got 2000 testimonials on her side, and we want to be able to search through them. What fields do we want to search through? We want to be able to search through quote and the attribution on what this is going to dio . It's a hey, I know that there's search built into this. And so, um, there it is the search bar and let's type in G R shows up with Ger. Let's type in Yoda, and it finds Yoda for us. But in the future, we're going to want to be able to select some of these. We want the content entry person or a client to say, Oh, actually, I only want this particular quote from Yoda to show up on our page, and right now they have no way of doing that in way. Tell, there's this feature called a snippet chooser panel, and as a stream filled, it's a snippet chooser block. And what that does is if we register this as a quote unquote snippet thes, then become reusable pieces of data with a graphical user interface inside of Wag Tail. We'll tackle that bit a little bit later, but for now, let's just go ahead and register this as a snippet. So all we have to do now is go back to our models dot pie and at the top here. We're going to want to register this as a way till snippets. So that comes from wag tail dot snippets. Top models, import register snippet, that one right there, and we can use this as a decorator or occasionally you'll see this used as a function. And then you could put your testimonial in here like this. We're not going to use it as a function we're going to use it as a decorator in Python because everything is an object you can use them sort of interchangeably. Use one or the other. Don't use both, though. Let's go ahead, Refresher page and all of a sudden way. See the section called Snippets Show Up. Now snippets again are reusable pieces of data. We have a section called Testimonials. It understands that we've got testimonials in here is called Testimonial is using the verbose name plural testimonials. It's click into that, and now, at this point, we can select a bunch of Leeds. Let's make that just a little bit smaller. Set looks a little nicer. We can select a bunch of these weaken, delete a bunch of them at the same time. We can edit them. We can delete one at a time if we wanted to. But the nice thing is, eventually down the road, just like a page chooser panel or an image chooser panel, that little motile that pops up in allows you to choose a page or an image. We're going to be able to say, Hey, show us a motel where we can select just testimonials, so that's coming up, down the road. So essentially what we've done is we've created a jingle model registered it with Wag Tail . So waggle is now our at men panel. So we don't have to use the jangle Admit we have added multiple columns. They're sort of all we've added search, weaken, search through either the attribution or the quote column And then we registered it as a snippet and now our data can be used in a graphical user interface or within something like the get context function or the get context method. 17. Welcome to StreamFields: Let's talk about stream fields. I've mentioned this over and over again. This is a concept that makes way tell extremely powerful. Now, stream fields are an insanely beautiful feature. They let you add sections of contents pretty much anywhere on your page. You can add unlimited stream fields, and your content does not need to be in any particular order. So if we view our Rocket Man page right now, we have header a hero section, nothing in here and then a footer section that is all in order. And that's going to stay that way because we needed that way. We need a constant template or a layout for our entire site. But right in the middle here we have all sorts of different content and we can mix and match it with a stream field. No one example of this al be It is probably a pretty ugly example. Is that one in here? Yeah, here we go. So, on the learned wag tail dot com website, this is a tutorial detail page that I have. It automatically grabs the tag name and the page name, and this one is a title. This is a stream field And then I've got a rich tech section in here so I can add links and images and things like that. And then I've got another title in here, some more basic text. And then I've got a code section and I can write any sort of code. I want again another title to more text and some more code. And I can mix and match this in any way I want. So this is No, this is not the greatest example. Let's see if I can find a better one. Okay, I found one finally, how to register a jingle model with wag tails model admin, which actually think was our last lesson. So if you ever looking for source code, it's on this page. So you can see here. I've got a title just as the jest and I've got a stream field where I can literally just put the gist. You are Ellen here. So I've got just your all, and it automatically does the highlighting and stuff from a get hub. You, Errol. I can mix and match us all they want. And just like that other page, I could move this up and down. I could add content between sections. I could add titles wherever I want. There is no strict formatting, and that is what a stream field allows us to do. But I guess an even better example would be, let's say, landing page. So let's say you have a landing page where you're trying to convert someone. In fact, I actually just thought of an even better example. So here's a wag Tail site. It's from amplifier giving dot org's so they've got a banner section here. This is not part of a stream filled because there's always going to be a banner here, but they can swap out the images and texts and things like that. Then we've got a section in here that they can move up or down. So they have this section of the three cards, and they could move it down, section up a section or delete it entirely. And then they've got another stream field here. Why started giving circle? They could move that up or down that can customize everything within it. Then they've gotta sign up, sign up for a newsletter to receive philanthropy, resources training and learning opportunities, grant opportunities posted by giving circles, and it asked for an email address. Then there's another section in here a stream field, another one in here and one of the bottom. And we could say, You know what? Maybe we want this green one toe. Also be up here right in the middle. Being able to switch your content around like that makes your pages really, really flexible while maintaining a nice design. Now, if you are already familiar with Django Stream fields, we're going to look a lot like a standard ah model mixed with the jangle form. But if you're brand new to jangle, your brand new to Wag tail is going to look a lot like what we've already worked with with a slight difference. And that's like difference, for the most part, is instead of saying models that text field, we're going to end up saying blocks, text block and instead of blankets false and Noah's false, we're going to be saying, Is it required true or false? Other than that, almost nothing's going to change. Everything we've been writing is going to be already very familiar to you. Now in this video, we're not going to be writing any stream fields. I just wanted to give you a quick let introduction into sort of what a stream field is and why it's important. And then, as one last sort of important notable stream fields and how they're stored in the database right now in her testimonial block or a testimonial model, we've got quotas equal to models dot text field in our database. Whatever database queen abusing this is going to be a text field or a text column with a maximum length of 500. The attribution is going to be ah, probably of arch our field or a regular character field with the maximum length of 50 characters you can also put into your fields. Positive interred your fields, Foreign keys many to many relationships. Things like that and those are all stored in their own columns. But a stream field is not stored like that. A stream field is basically all of your stream. Field data is going to be stored as a giant Jason object, and the reason for that is because this allows your page to look up all of its custom fields really quickly and maintain the order of all of your content while still only performing database queries when it's actually absolutely necessary to. But as a python developer that could make parsing your stream field data. Ah, little bit tricky because it's all J solidified, and it's a little bit harder to pull it all over the database the way you would expect. So there's mostly pros. It's 90% prose, but there is one little con there. If you're looking to really automate your stream fields and you want maybe someone to fill in a form on your page and create a stream field down the road, maybe that might not be the best approach, because that's going to be a little bit harder than just using a regular field and just updating a regular model. Regardless, let's go ahead and jump directly into streams, fields 18. Creating the StreamField App: like anything in the world of programming, there are several different ways to achieve the exact same final outcome. In this course. We've been putting all of our pages into their own APs instead of one big app called pages . But, hey, some people like to put all their pages into one app, and that's totally OK. I'm not going to tell you that's right or wrong for me. I like to split things out so that I can find them a lot easier. The same idea can be applied to stream fields, although I'm going to be creating an app just four stream field so that we don't run into circular imports and we have a dedicated area for all of our stream fields. It helps us find our custom content sections. Ah, lot easier, I find. So instead of spreading them out into stream, feels that are just for the home page without stream fields in the home app. And then we've got stream fields in the services apple. We've got stream fields in, like I don't know the flex page app. We're just going to put them all together so we can find them nice and easily So what I'm gonna do here is just get into my virtual environment. Source. Rocketman vin been activates. That's how we do it with. Then let's go ahead and do python manage dot pie. Start AP Streams and Streams is There's going to be a short terminology for the word stream fields, and let's go ahead and do Python managed at PI runs over and let's do that on Port 8000. Let's open up our code and we can see that there's a new section in here called Streams. Let's go ahead and activate this new app, so enter based up high in our installed APS. Again, we've done this number of times already. Let's go ahead and add streams in here. Save close that and let's go and look for streams Now. We actually don't need a lot of this. We don't need views. They're going to have their own templates, and stream fields are also going to be included on different pages as well. We're not going to be writing any tests, so we don't need to do that. Models while stream fields are sort of their own things, So what we're going to do is rename models. Two blocks. I guess you could call it models if you wanted to. I like Colin. It blocks APS. We're going. Teoh, keep admin. We are going to delete. So we have migrations in it. APS and blocks and in blocks. I was gonna delete that. And now we have what looks like a jangle app that we sort of mutated quite a bit. So we don't even have a model stop high file anymore. It's just block Stop, I Now, the reason I'm calling it a block is because every stream field, every stream field that we end up creating is actually a block of some kind. So maybe it's a char block or an image chooser block or a choice block. We're gonna go through a bunch of these different blocks together, though, but for now we just need to make sure that we have streams up and running, that we have streams enabled in based on pie and that we have a block stop. I file once you have that head on over the next lesson and we're going to create our first stream field 19. Your First StreamField: Oakley locally, let's go ahead and create a first stream field. This is going to be a basic text based stream field, So let's go ahead and open up our streams block. Stop, I file and let's create our first struck block. And what I'm saying here is struck block. Now we're going to create a struck block with just one single field in. It's just going to be a regular text field. And instead of using something like models Dutch, our field, we can use something like blocks dot hr block. And this is strictly wag tell at this point, this is always tell all the way. Now, at the root of it eventually breaks down into Django. But this is pretty much always tell right now. Now the nice thing about stream fields is they can be sections of content that can be ordered up and down so you can say, like hello, World is the 1st 1 My name is Caleb is a 2nd 1 I like goats is the 3rd 1 and it's just like moving this up and down. But you can do this with entire blocks of content. That's one real nice thing. The other nice thing about this is that stream fields could be nested, they can be reused, and actually, we're going to get into that a little bit later. So let's go ahead and get started with this. The first thing we need to do is import this. Let's do from wag tail dot core important blocks. No notice that this is using the word blocks and our file is called Blocks up High. There's no relation there. It just happens to be that I called this blocks up. I It does not need to be called block Stop I. This is simply where I'm storing this file, and Wag Tail also has a file called Blocks That Pie. Now let's create our first class here a title blocks. Let's call this class title block and is going to inherit from blocks dot struck block, and it's going to have one single field in here. Just text text is equal two blocks blocks dot hr block, and in here we can say instead of blank is equal to true or false or no is equal to true or false. We can say this is required. True or false. Yes, this is required. That's the default. Can actually see it here, says Char. Block requires equal to true. That's the default. I can also give us some help. Text max length, mean length, custom validators, things like that. Let's go ahead and give us some help. Text. It was going to say help Text is text to display. We don't need panels. We don't need anything like that. Wait till just automatically assume that if you have a stream field here, it should be shown. Let's go ahead and work with some meta data as well. So, first of all, we can give us a template, and that's going to be somewhere. We'll get back to that in a second. We can give us an icon. We'll get back to that in a second. We give us a label custom name if we wanted to. We'll get back to that in a second and also some help text, which, if you haven't guessed it, we'll get back to that in just a second. So the template is going to be just like our way. Tell pages if this was awake till pages would be title blocked out each to melon, it would live inside of the Streams folder because this is the streams app. So this is where it would look for it automatically. But it's not a wag. Tell Paige it's a stream field and is going to do something very, very similar. But instead of guessing, I always like to just put that in there anyways. So I'm gonna assume that all my stream fields are going to live in the templates folder in a sub folder called Streams, and this is going to be called title block dot html. I like doing that. So if you were to look at my code for the first time ever, you'd be able to look at this dream feeling ago. I know exactly where that template is. Let's give this an icon and edit icon, and the label is just going to be title. Otherwise, it might be called Title Block or anything else we want to call it, and the help text is going to be centered text to to display on the page and that it that's how we create a stream feel now that's not available on any single page at all. So if we go back here and we try to at it our home page, going to see that nothing's changed. We added lead text button button text, banner, background image, but we haven't added a new section in here. This is where the panel comes in to actually expose the stream fields to the admin interface. So let's go ahead and add this to let's just say the home page so that our homepage can have some custom content on it. I'm gonna go ahead and collapse that left panel there, so I'm just on home. Models dot pie and in here I want to do from streams dot blocks. That's my blocks file here, not the wait till core one. That's my blocks file import title block, Tyto Block. Alternatively, we could also do from streams import blocks and then a little later, instead of just using title block, we could use Block start title block. In fact, that's the way we are going to do it right now. So we're just going to do from streams import blocks next in our home page down here. Let's go ahead and add some custom stream fields. Now a stream field is its own field type. Just like we have a char field or a text field. This comes from Wag Tail. This is a stream filled. So it's called this body. That's what we're gonna call it. This is going to be a stream field, and this is going to take a list of topples. So it's a class. Its first parameter is a list, and in here it's going to be a bunch of topples. So let's call this one title. It is a to set topple, so the 1st 1 is going to be what it's called in the database. So again, remember how I mentioned that wag tail stream fields are all stored in Jason? Well, that's the key. That's how it knows what it's going to be. So we're gonna call that title. You can call it anything you want. It doesn't have to be title, and we're going to say blocks dot title block with parentheses because it is a class. I just throw a common there for good practices, I guess. Are we gonna let this field be knowable? Yes, we're going to say that the home page might not have any custom content on it and kind of be blank. Let's also say yes. That means you can save the home page without adding any extra stream fields are or custom content to it. Now we have this body. What do we do with this? Let's go down to our content panels here, and we need to expose us with a new panel. So is create a new line of even move that up a little bit there. Let's create a new panel called a Stream field panel, and we're just going to put body in here now. If we save us, it's likely that our terminal is going to complain because, first of all, that was a table. It's gonna complain about stream, field panel and stream field not being imported. So let's go ahead and make sure those air imported. So in our edit handlers up here, we've got field panel page chooser panel that's at a stream field panel in here, and the stream field itself actually comes from Wag Tail decor dot fields Import stream field. All right, let's say that and see Hey, look at that terminals not complaining about anything. Let's go and view the page and, as expected, operational air. No such column. Home home page dot body. Basically, this is an SQL area saying that our home page does not have a column in the table that it lives in cold body. Let's go ahead and make our migrations make migrations, apply migrations, rerun our server. Let's go and refresh your page and we're going to see once it loads want loads, Scroll all the way down. We've got a new section in here now, if you were using Wag Tail 2.6 or earlier, this is going to look slightly different and for using Wait till 2.7 or newer is going to look something along these lines. So I've got a new section here called a title, and I could put anything I want in here. That's a wheel. Make your rockets more fuel efficient, centred text to display on the page that their help text. We've got some more text in there. It's a required field. Let's go ahead and save this. Now, when we view this, we're not actually going to see this at all, because on our home page template, we haven't specified that we actually want to show this at all. So our next step now is open home page dot html. That's not the right one at all. Home page dot html That's our homepage. And on our home page, we have a section for our banner. I'm gonna make that just a tad smaller there. So I've got a section for a banner. Let's collapse that and let's create a new block for a content. And again, just as a quick little refresher here. If we open up based on HTML, we've got a section in here just for a content. We're going to inject our stream fields into here, and to do this is actually super super easy. We just need to basically loop over each one's, a four block, in page dot body include Block the block that were looping over. That's that value right there and and four now for this toe actually register because this is a wag tail function. This doesn't come with default, Django. We need to make sure that we can use us. So let's also load along with our wait till images tags wag tail core tags. Let's save that and let's refresh your page. And now we can see that the template does not exist, so we're actually getting somewhere. This is saying that our Streams Air title block does not have a template yet. Let's go ahead and add that template. So open up my website and then go into templates. And in here it was looking for streams slash title block dot html. Again. Where I got that from was I specified it specifically to live here, and you can see where it's looking. It's looking in templates. Streams title blocked out. Html It's also looking in home templates. Streams Title block. Daddy, She male. It's also looking in search template streams. Title blocked out. Html Let's just put in. The first place is looking That seems most efficient. So we've got a title blocking here. Then let's just do something real ugly. H one, this is a title block refresher page and oh! Oh, there it is. There's a title block. Okay, Okay. We're getting so close to this. Okay, so I just threw some html in here with an H three and a custom class on that age three. And now I could do say anything in here. All right? No says say anything in here. It's looking a little more styled. Let's go ahead and actually throw that text in here when you use value in lower case dot text. Now, if you're wondering camera, where did you get value from? I can use value or I can use self, which everyone makes more sense to you there the exact same. And that's actually coming from our block itself. So title block and put these together Title Block is using this template and inside of it, just like page dot text or page dot banner title. This one has title block dot text. Now it's not a page. So we're not gonna call it Paige to stream fields. We're gonna give it the value. The value is whatever the stream field is or self dot whatever the field name is again, I'm going to call that value. Let's go ahead and refresh this page. Well, make your rockets more fuel efficient, and that is exactly what we put in her home page. Now let's take a look at the rial reason we're actually using a stream filled for this Is I put anything in here, I'm gonna put alarm ipsum in there and below it. I'm gonna put this is below the original one. And let's preview that alarmism at the top, the original one and one at the bottom. And if I didn't like that, I could always move this one up. And so the original one is now at the bottom hit preview again. Scroll on down. Well, make your rockets more fuel efficient. It's now at the bottoms. Now we're taking these entire sections and moving them up and down. This is just where it starts because this is just one title field we could do is with entire sections of content with images and videos, documents, links, all sorts of things. I'm gonna close that preview and I am going to I'm not going to save this page now. One last thing. When you're working with stream fields because they are a special, complex type of field, it doesn't just have a regular column with a single value, and it is a big Jason blob. It's always a good idea, and I don't think this is going to need any migrations, but I think it's always a good idea to when you're done working with them. Always do managed up high, make migrations no changes detected. But if you've been working with them, if you've done anything extra, you will have changes detected, in which case you will also run. Python managed up high and to run them, you use migrates. Correct? Yeah, use my great, and that will apply your changes for you. Let's move on to the next one, where we can create sections of stream fields where we can add more and more and more and more like a list. It's actually called a list block. So once you're done playing with this, if you want to definitely feel free to experiment with a code and when you are nice and ready, let's head on over to the next lesson where we pick up our next extreme field type. 20. Repeating StreamFields: There's going to be a time when you need to repeat similar content over and over and over again, sort of using the same template and swapping out different parts of the content. Lists of really anything are really good candidate for this thing called a list block. So in the last lesson, we learned about a struck block. This one we're going to use a list block, and this one has some weird looking syntax. But it is a very, very powerful feature in wag tail stream fields. So on our website were going to be creating a repeating content block where the content entry person can add on limited items if they wanted to. But they're all going to be formatted the exact same way. And we're going to be doing this with bootstrap cards. So if you don't already have your server up and running, make sure you get into your virtual environment and you are running your Django server. Next, I'm going to open up block Stop I That's in my streams application. So streams slash blocks that pious where this is that and this is where we created our title block. Next, we're going to create a new type of stream field. This one is going to be called a list block. It's actually still technically a struck block, but we're gonna throw a list block inside of it, and then we're going toe loop through it over and over and over again. So you can think of a list block, really as a really fancy list. And then inside of this fancy list, we're going to have another struck block. So again, the syntax gets really weird, and I'll try my best to describe it as we go along and this is going to at first. Not only is it gonna look weird, but it's going to sound a little complicated. Probably already sounds complicated if you're brand new to Wag Tail. But once you write a few of these, they actually start to make a lot of sense. So let's start with a very simple example. Let's add a title char block and a text text block and add ah meta class Wissem some data in there. So let's go ahead and create class. We're gonna call it cards block because that's what it's eventually going to be. Calder. That's what it's going to be doing. And in here, this is just going to be a struck block, a regular struck block. Now, here's where it starts to get a little weird. So we're gonna have cards in here, and this is going to be blocks dot list block. So this is already starting to look a little bizarre. And then inside of this list block, we're gonna do block start struck block. Now, this is where it gets weird because our classes of struck block were defining a list block . We've got another struck block inside of it. And in here, this is where we're going to put all of our custom fields. So this is a list of couples just like a regular stream field, just like how On our home models that pie we have stream field and it is a list of two pair troubles. It's going to be the exact same thing. Here is a list of two pair couples, so the 1st 1 is going to be a title comma, and then we're going to use blocks dot HR block looks a lot like blocks HR block from our title block. This is going to need a max length. We want to give us a max length of maybe 100 or so, and we can give it some help tax, so help tax is going to be Eventually. This is going to be the Bolds titled text for this card, and I like to put what the max length is so that your customer, your clients, continental people know what to expect. So there's a max length of 100 characters Now if I really use remote, I mean, there's a really long line, and eventually I would break this down. But for now, I'm gonna keep this on one line because I'm also going to create some text in here on a second line. If I put this on too many lines, that starts to look a little complicated. So this is going to be blocks dot text block. So instead of ah, text field, it's a text block. Max length for this one is going to be a whopping 255 characters, that is all, and the help taxes going to say the optional text for this card, The Max Max length is 250 characters, and let's also say that this is optional by saying required is equal to false. It was as, um, metadata in here. So class meta, I'll make that bigger one more so we can see that a little easier, you know, collapse that. They're So we've got class meta. And here let's specify template, just like we did with her last one. We're gonna say it goes into streams and this is going to be a cards block dot html and something is not right because my syntax highlighting is broken. What did I break in here? There it is. I am missing a bracket that looks better. I'm going to give this an icon of an image and a label of ANA standard cards. I mean to say that make sure my terminals not going toe complain. And sure enough, it is about a stuck block. I think that's when I made the original typo. Let's go ahead and restart Django. And nope, sure enough, stuck block is not a thing. Where is stuck? Block? There you are. I was looking at the wrong one blocks that struck block Okay, terminal and is happy with our code. Let's also go back to our home slash models dot pie. You can see that I'm in the home folder models dot pie, and now I'm going to create another two pair toppling here, and this one's going to be cards. And this is going to come from Block Start cards block and again. Just put a comment The end for good practice. Let's go back to our home page and let's go edit it. Gonna wait for it to load. Let's create a new one in here and look at that. We have a title block, the one that was created in the last lesson. We also have a standard cards Now. This is where it gets really cool because this looks a little nested now, So this could be title number one, and text could be any sort of Texas completely optional. So let's say the 1st 1 is going to be optional. Title one with optional text and let's go ahead. Hit that plus sign and create another one. And these are actually and totally swappable, too, so we can swap these up and down, and this one's going to say, title number two, because I'm not feeling creative with my titles and this one. We're gonna have some warm ipsum text in there. Let's go ahead and save this. Now I'm going to view live and it's going to say I know it's going to say this can't find the template. Sure enough, look at that template does not exist. It's looking for streams. Cards blocked out. HTML. Let's go ahead and create that file now. So in a rocket man templates, we already have a folder called Streams. Let's create a new file in here called cards block dot html and let's just refunds is Let's just loop through this just real quickly. We're not gonna make this look good yet. We're just going to loop through this, so I'm going to say four every card in value dot cards. We could also do self dot card, so I'll use these interchangeably throat different stream fields, and I'm going to end that. So what I'm doing here is I'm saying in this particular stream field, no wrong file their Rio. In this particular stream field, I have a variable called self dot cards. Self dot cards. Object oriented programming dictates that if I were to access this in a method, it would be called self dot cards. It's a list. Block is right in the name. It's a list, so it's an irritable weaken loop through it so we can say for each card in self dot cards. And then we can go and get each one of these So we can say the title is going to be an H three card dot title. And we can also say, if card dot text we can put paragraph in here card dot text and that if and let's also add a bunch of horizontal rules to make this really, really ugly, but also very noticeable with where we're working, Go ahead and refresh this page. Look at that title one optional with no tax. There's no tax in there, entitled to with some text in there in a bunch of horizontal rules to make the page super ugly. Now let's back this up. Just a quick second here in her models don't pie. We added something to our body field. Why did we not apply migrations? Well, because this is just basically a text field. Storing a lot of Jason in the database doesn't necessarily need migrations to be applied, although chances are, if you were to try to run migrations right now, it would say that there were changes detected. We're going to run migrations at the end of this lesson anyways, but we don't need to do it right now, all right. I think we have a little bit of time in this video still, so let's go ahead and add a couple more items in here. So we're going to say every card as a title. Every card has, um, text. Let's see. Every card has an image, and we're going to use an image chooser block we have not imported this year, not a panel image. Chooser luck. This is going to be required. Two required by default so we don't have to put required is equal to true help. Texas Eagle to Image will be auto magically corrupt to Ah, if I remember correctly somewhere around 570 pixels by 370 pixels and we're going to set this cropping in the template, let's go ahead and save that and let's refresh our creditable page or home page. Oh, it's not going to load because I didn't add image chooser block, Let's go ahead and add that in here now. So we do from way tell dot images, duck blocks, import image, chooser block. There we go. Now it will connect. And if I scroll on down, we have a little image section in here. So I'm gonna say that's gonna be the 1st 1 And this is going to be the 2nd 1 going to go ahead and save that. It's going to when I refresh the page is going to loop over everything. There's no images because we didn't tell it. Toe have any images? Let's let's go tell it to have some images now, before we can use images at all with Wag Tail and automatically cropped them and all that good stuff, what we need to do is say, load, wait till images tags, and that allows us to use a template tag called Image. Let's put that in a for loop. And because we know this is a required field, it will always be here. I'll say four. Every image that we have we're going to use the image tag card dot What did we call it? What did we call? Probably just image. Yep. Sure enough, just called it image. So I just got that from the first value in our double sets of couples here. So the first set in our Topple is called image. This one's called Texans called Title. That's where we're getting these from. So this one just called image, and I'm just going to use that there. I'm going to use the film method. There is Max with methods, height and width methods as well, so you can set a maximum height or maximum with on your images. Things like that were going to say Always feel this toe. Always be 570 pixels by 370 pixels. And then we're going to simply store that as a variable called image. Let's go ahead and create a new image in here and for the source. Let's do image that you are on the altar is image dot alta, and so I am G matches here, here and here. It has nothing to do with the HTML tag, and in fact I can change that just to prove it. We'll call it the image. Let's go ahead, save and refresh our page, and we're gonna see an image there it is. There's our 1st 1 and there's our 2nd 1 And if I inspect this element here, sure enough, there it is. 5 70 by 3 70 it took my image and it cropped it perfectly. Now I think that's enough for this particular video. In the next one, we're going to start abstracting some of the more complicated parts into other classes that we can reuse. So in our blocks, we don't necessarily need to put all of this in here. We can make it look more like this one up here because this one down here is honestly, that's kind of hard to read. So we're gonna touch that up a little bit. And I also mentioned, at some point we can run migrations had. Let's do that. Python managed up high. Make migrations. Let's see what happens. Sure enough, Alter field body on homepage and let's apply that migration. Everything is well. Don't forget at any point time. If you get stuck, always look at the source code. It's completely available to you. Otherwise, let's head on over to the next lesson where we continue to operate inside of our list. Block 21. Simplifying Repeating StreamFields : all right. Picking up from the last lesson. This is the second part of the repeating stream field lesson. This is a fairly big topic, so it's split into at least two videos. Now, if you haven't watched the 1st 1 you're going to need to watch the 1st 1 Otherwise, you're probably not going to know too much about what's going on with this one in this video. So now that we have a simple list block style stream field, basically we've got struck block with this block, instruct lock in it and then sets of temples in here. Let's go ahead and make this even more complicated, so the first thing we're going to do in here is add a link and we need to add three fields . We need to add a link text because we want the text to be customized. We want an internal link to another wag tail page or we want an external link to a page. It's not on our website, so let's go ahead and sort of just hash this out very quickly. So we've got block start HR block, and we can fill this out in just a second. Let's call this link text. The 2nd 1 we're going to call Internal Page, and this one is going to be some sort of paid shoes or we don't really know that one yet. We'll come back to it, and the 3rd 1 is going to be an external link. And in here, this is Block. Start your L block. Just like, uh, you are all field models that you are all field. This is a blocks that you were a block. Okay, So is the external link going to be required? We're gonna say no, because the links are going to be optional entirely the internal page. How do we choose an internal page? Wait till gives us a beautiful little tool here called the Page Chooser Block, who can also say required is equal to false Make sure you gotta calm at the end of that and our link text. We need a max length. Let's say 50 and default is going to say more details. Also put a comma at the end of that. Let's see. Okay. Presumably I don't have any typos in there. Let's go ahead and edit our homepage. And in here we can see there's link text and internal page, an external link. So on internal page allows me to choose the home page or the Services page or the about page or any of the service detail pages. So for this one, I'm going to select cleaning. And for the 1st 1 I'm going to just put an external you earl in here. So, http wag tail dot io All right, that saved. And when we view our page again because we haven't modified anything, a template, we're not going to see any of these changes yet. So let's head back on over to our template and let's create a Lincoln here so it's create an A tag. The draft is going to be a little bit of template logic. So let's let's leave that for last, I suppose. And in here, let's put our button text. We've got some Texan here called card dot Link text, and where I got that from was for each card in cards. So that's where I'm getting card from. There's title text, image link text, internal page, an external link. I'm getting the link text from right here, and I'm just throwing that in there so Let's go ahead, save that and refresh our page. And we're going to see more detail for detail. And in fact, that is supposed to say more details. But that was already saved. So that's too late. Ah, and now we need to check to see if we have an internal page or an external link toe to use . So let's do this in here. We can basically, right. I'm gonna write this out the long way and then I'm gonna shove it all into a one line going to, if card dot internal page car dot internal page that you are l. Because it's a page object and page objects always have dot u R l In them, they always have the UL property. Then we can say, LF card dot external page, Where does that come from? There you are. External link is what it's called. Then we can say card dot external link that one doesn't have a dot UL because it's just a regular text field. And if that's what this is going to look like now I'm going to start toe jam all of this into one line. The fifth ISS and copy all of that and I'm gonna throw that into the A draft. Here it's Go ahead, save and refresh that page. And if we look at the bottom left, that is in fact trying to go to wag tail dot io. That's perfect. That's where the 1st 1 was supposed to go and this one is going to go to the cleaning page . So now we're able to actually add links to our stream field inside of our list. Block can go to any page inside of white, tell any page outside of wag toe. Now, let's go ahead and make this code a little nicer to look at, because if we look at it right now, I mean, this might be readable for a lot of people, But to me, this sort of just looks like garbage. Like if you showed me this code at first, yes, I would read it, and I would be able to parse it with my own brain. But just looking at, I just go. What is what is all that stuff down there? This this doesn't really look great. I don't really want to tackle that. So let's go ahead and sort of like extract all of us, and we're going to put this into a new class called a card, and this is going to be a struck block, and what I can do is I can take all of that copy and paste, and we cannot use it in this particular this format. But what I can do is I can use my editor to help me sort of cheat a little bit, and I can at its multiple places at the same time. And we can do this. We can select all the blocks that's just grabbing the R block. Let's grab all the blocks. But these all in new line, that's not quite gonna work out the way I had hoped it would. So let's go ahead for this on new loan. Get rid of those two lines. All right, so here you can see, I just put everything sort of on their own line, and it looks a lot more like our title block. It looks a lot more like a wag tail page looks a lot more Django friendly, and it doesn't need to look like this anymore. Now we're not actually going to enable this, as is own stream field. We're just going to use this in place of all of this stuff. So what I'm gonna do is I'm going to delete that, and I'm also going to delete this and I'm simply going to put card. It is a list block of cards. Now, when I go back to my browser and I'm gonna edit this page Ah, I still have all these cards in here. So I got the title, the text to image the link text, internal page, all that good stuff. So just to make sure that this is working, let's go ahead and throw something crazy in here. Zebra, zebra, zebra. I don't know why I picked a zebra and let's go ahead and refresh your page and look at that . So now we've entirely actually we've we've entirely removed all that noise, all that no easy code, and we've put it into its own class. Now, the nice thing about this is we can actually go one step further and we can take out our link as well because we're going to be using that in several different places. So I'm actually gonna cut that and I'm going to create one more class in here class. It's just called a link blocks dot struck block and throw that in there Now, a little bit later, we can actually even get one step further, and we can add validation to this. So any time someone uses a link on any Stringfield anywhere, we can say that the internal page or the external link have to be. Sadly, one of them absolutely has to be set. Currently, they're both not required, so you could theoretically have a section without a link. Well, you don't really want that at all, so we could make sure that that's always said We'll talk about that down the road. So we now have a struck block for a link, and instead of our card having the three fields in here and sort of looking fairly long, we can know just say link is equal to our link. And is there going to be any sort of help text? We could do help Texas equal to enter a link if we wanted to enter a link or select a page ? So now if we rewind this, our cards block is a list block of cards, and that's just using this. Every card has a title, text, image and link. And now that link also has a link text, internal page and an external link. Let's go ahead and edit our page. We are going to see one change in here, whereas if I keep scroll and down, keep scrolling down. We've got a link section in here. Enter the link or select a page with link text, internal page and the external link. Now, if you remember, those links were actually working and you can see at the bottom left here. I haven't refreshed my page yet. It is working. It's his way. Tell dot io and if I refresh my page, we're going to see that link no longer works now. The reason for that is because we said in the card block, we no longer have car DOT external link. We have card dot link dot external link or card dot link because it's thinking to another class dot link text. So let's go back to our template and where it says car dot internal page or car dot external link. We're gonna grab all of those and do card dot link dot internal page card dot linked out internal page dot You are Oh, so it's getting kind of long. Were sort of starting to traverse through our stream field into Wag Tell itself or we've got card that link dot external link. Let's go ahead and save that refresh our page and you're going to see that this still doesn't work. Now it's at this point you might be thinking that something is broken and you wouldn't be wrong to think that. But the reason this doesn't show up again is because of what we originally saw. This is actually it's own section now, so link text technically does not exist. It is linked dot link text. It is linked dot internal page. It is linked dot external page. So if we choose a page here and this one's just going to go to home page and this bottom one is going to go to wag tail dot io and let's put on external Lincoln here. Http wait till dot io save in refresher page and they still don't show up. And I am willing to bet that they are in their working and that I had just made a mistake. Let's go ahead and dissect this. I bet you I made a mistake somewhere. And if you're screaming at your screen saying Caleb, I know your mistake is that's awesome But I'm going have to find it low. Look at that. There is the link. No text in there. That's interesting. It's probably the exact same thing down here. This is gonna goto yet. Wait til dot Io no link. So that is my mistake, and my cursor was already on it. It was card to dot link darling text. Let's go ahead. Save that refresh one more time. High like that. Sure enough, bottom left, says wag tail dot io. That's where it's gonna take me when I click on it. It's going to take me to wait till dot io Check that out, and that top one is going to take me to the home page. So as a quick recap, what we've done in these two videos is we have created a struck block with a list block inside of it, effectively creating a list of struck blocks. We originally created a fairly long form struck block, and then we sort of abstracted it out of itself into its own class. We call that class card and said, Just make a list of card struck blocks. But we also had links in that struck block in the card struck block, and we said, That's that's too much. So let's go ahead and actually turn those links into their own class, their own struck block, which is what we did up here. And we threw those in there. But now, because we have for each card in self dot cards and we have link going to another class in our template were using card dot link dot link, text card, darling dot internal page. And it's really just saying for each card in cards dot link dot link text and that, my friend, is a list block. So again, the syntax that versus pretty weird I'm not gonna lie. It's pretty odd, and it's a little confusing at first, but definitely give it a shot to try to make a few of your own. And when you start to make a few of these on your own, you start to look really nice. Now, this is not everything I've wanted. This is not a nice looking stream field. So what? I'm going to Dio is I'm actually going to fade this out and I'm going to throw in a bunch of HTML and we're going to fill this in together. All right, So I just added, Ah bunch of HTML in here and we're gonna fill this out together. So this is just a standard sort of bootstrap block. So it's probably not going to look the greatest at this point. Yeah, here we go. It's gonna look just like this. So we've got a card on the left card on the right because we have two items and our list block. We've got a title, some text, Ah, button and link. And this is supposed to go somewhere. So let's go ahead and actually do something with this. So what's the biggest win we can make here? What is going to really liven up this display? Let's add an image. So we have our wag tail image tags loaded. Let's go ahead and add our image Image card dot image. And again, I am getting that from card dot image. Phil, I've got a placeholder in here for 5 73 by 3 69 Let's do 5 73 by 3 69 Although I think that helped. Text is actually saying something different, isn't it? Yeah. 5 70 by 3 70 Let's just make that right. 5 70 by 3 70 as image and let's replace this. I am G. Don't you are l and the altar. Let's change that to I am GED tot not you, earl dot lt Refresh your page. Look of that. It's already starting to look a lot better. Let's have the title next. We had a title in here. This is just going to be the card dot title, and again, I mean, this might make perfect sense to you, but I'm really going to drive this home. Where do I get card? That title from, I mean the list block. It's using a card class card dot title. That's where I'm getting this from the text we can do card dot text. Let's make that lower case, and is that optional? Sometimes I make that optional required false that is, in fact, optional. So let's throw an if statement in here. Let's do if card dot text. So if there is some value in there and if and let's refresh our page that's coming along pretty nicely so far. Let's now add that link. That link is going to be the Link text card dot the link dot link text. And again, where did I get that one from? I've got a card list block here. I'm looping through each one called card. Still four card in cards. It card dot Link is in here, so each card dot link dot link text that's where I'm getting this from. And now I need to fill out these links thes age refs. So let's do a little logic in here. Let's do If card dot link died internal page, then we're going to use the internal page you or else we're gonna do card dot link internal page dot You earl else. If your elliff card dot linked that external link, is that what I called it? External and internal page external link. Let's go ahead and do card duckling that external link and lastly soon else statement in here. Let's make it go. Absolutely nowhere with a number sign. And if and hopefully I got all that syntax, right? If I didn't Jenkins gonna let me know because there is going to be a a little hair on the page. Sure enough, nope, That all worked. Although when I hover over here, it says local host of the bottom Left, It says local host card dot link dot external link. Something is not right that supposed to be right there and check that out. That goes to wag Tail. Everything here looks nice and custom. This one's going to go to the home page exactly as I expected. Click that. Make sure it does go to exile dot io and look at that. We have got nice cards going on repeatable cards. We can add as many as we like. You could add hundreds or thousands of them if you wanted to slow down your page after a little while. But you can add as many as you like. I'm going to leave this at two and moving forward through this course. I might actually touch up some of this content as well, so you might not actually see in like the next video that this says title to it might say something a little bit different. Next. I think we should continue on this rule that were on, and we have a little way of adding some extra logic here. So instead of saying if card dot linked dot internal page else card not linked on external link yada, yada, yada, all of this stuff, instead of saying that we can actually shorten that up, I think we should go ahead and do that with a thing called a struck value. We'll do that in the next video, though, so once you're ready to move on from this one head on over there and we'll learn about simplifying some of this template logic. 22. Custom StreamField Logic: with a struck block, we can add custom, logic and values without having to actually map it back to a stored data object, which is really just fancy talk for saying we don't actually have to store extra data like we don't need another fielding here called, like the official you, Earl. What we can do is we can add some additional logic in here now, In Django. If this was a model, this would look something like class. You're model models, that model. And it would be like you've got a property in here deaf, something self return. You know, your logic would be in here, and then you would return. Whatever else is in there. Basically, you would use the at property decorator. Well, we're not doing that because we're not using at property. We're not using a jangle model. We're using a wait till struck block, extreme field section. So essentially, what I would like to do in this video is get rid of pretty much all this logic and just say if the car darling you are el exists, use that you, Earl, whatever it's going to be. So the temple he no longer needs to check to see if its internal page or an external page it needs to be one or the other, and we're going to do them with a cool little thing called a struck value. So in our link, we can actually say class Meta. Let's give us a value class and we're gonna give us a value Class of link value, link value. No, this currently does not exist. The link value does not exist, but we're going to make it exist. So up here, I'm going to say class link value now is going to exist. Blocks dot struck value, not instruct Block nor a list block, but instruct value. And this is going to add additional logic for our links. And so what I want this to Dio is instead of saying, link dot internal page, I want our template to be able to say link dot you Earl, and it's going to figure out which one is here, and it's going to just return whichever one is available. Now, this is actually quite simple. We have to do is type. You are l self, they're deaf, You, Earl, and let's go ahead and get that internal page to the internal page is going to be self. Don't get Internal Page and because we've linked this value class to delink value right up here, whenever dot u R l is going to be called, it's going to say, Hey, grab that internal page, whatever that is, and store that in a variable that we now have access to that let's do the external link. Let's do external link is equal to self dug yet ex eternal link. And I like putting these in their own variables just because it seems to make my code a little easier to read a little bit down the road. So now I can say, if Internal Page, because this is a local variable, I can do that. If Internal Page is actually set, I could just return internal page, don't you, Earl? And it looks exactly like what's in our template we've got If car dot linked on internal page, then show car dot link dot internal page Here it is dot u r l. We could do the exact same thing in our python logic. We can also do LF external Link and we could just return that as well External link. And lastly, if we wanted to, we could just return nothing. And if we wanted to him to this a little bit, we can say this will always return a string. Let's go ahead and say that. Check out our terminal. There's no complaints, so I don't have any typos. Hopefully, let's go back to our cards in here, and I'm gonna put this near the title just so. It's a place that I can see put horizontal rule in there and let's do card dot link dot you , Earl. Now the reason I'm using car dot link that you are only is from the last two videos where we're talking about a list block. And so now we have a real chain of events going on here we have a struck block called cards block, and inside of it is a list block of cards. Inside of each card is a title text image and a link, and inside of that link is three more fields, link text, internal page and external link. But we're also saying, Hey, there's a value class here, so give them some extra logic so that value class that link value is gonna come up here and any time we do dot u r l on the link itself. So this is just like adding you were all is equal to some custom logic. It's just like that that custom logic is now going to look for the internal page. If it exists, it's going to give us the girl. If there's an external link instead, it's going to give us that. And if there isn't one is going to return just a empty string. How do you like that For a chain of events? That's pretty good, isn't it? So now we're doing card dot linked at u R L instead of car dot linked on internal page car adult thing dot external link. Let's go ahead and give us Ah, quick, A little refresher. This one says Slash, because it's going to the home page. And this one says http wag tail, don't Iook because there is no page set for this one. It is an external link. So now let's go clean that up. Let's get rid of that. And let's select all this logic from our last video, all three sections, and we can simply do if card dot link dot u R L card that link that you are well, otherwise there is nothing in there. So, in fact, we don't even need to do that at all. Let's let's just do this. Let's always put that link you, Earl. So even if there is nothing in there, that's totally okay. Sure enough, the left one still goes to the home page, and the right one still goes to wag tail dot io. And at this point, we can actually make our template look and feel a little nicer. I like doing this part because we can say at the beginning of our A tag if card dot link dot you, Earl and let's go to the end of it and do And if and let's also go and look for the closing tags and basically do the exact same thing If card dot link dot u r l. And I'll explain what I'm doing here in just a second. And if so, this is saying if there is a link, you well, so basically, if it returns anything other than an empty string provide us with a link with that link you where l inside of it and likewise down here were saying, If that link you where all does exist, also, close that link. So now if we save this page, we're going to see absolutely no difference. Thes both have links. These are both links. You can see at the bottom left that they're switching back and forth. But just for fun, Z's let's go ahead and get rid of the one that links back to the homepage. The one This is Zebra, Zebra Zebra, and this one is going to the home page. Let's clear that choice. So it's not going to the home page, and it's not going to any external page or internal page. It's Click publish and refresh our page, and this one is not a link. Actually did break something there, though, and that was because I was trying to be fancy and edit three places at once. And sure enough, the card on the left no longer has any links anywhere, and the card on the right still does. And that is how we add extra logic to our stream fields using a structure value. Now it doesn't have to just be with a link. You can do this on any struck block at all. All you have to do is do value. Underscore class is equal to whatever your link value is going to be your or really whatever that struck value is going to be called that class name, and then you can grab your values from whatever field you have written. Then you can perform some logic and return anything you want. Personally, I think this is a fantastic feature that I don't see enough out there. I really, really like this one. I think it's a nice, clean way of giving your stream fields a little extra logic without cluttering up your template. At this point in time, I would highly recommend taking a look at the source code, even just feel free to totally steal my source code and just tinker around with it. Experiment. Try to break it if you want to, because there are four sections to this. There's the cards block. There's the card block, there's the link block. And then there's the link. Value has struck value, so definitely give that a shot. Have some fun with it again. Don't be afraid to break it. And when you're ready to move on to the next dream field. Let's go ahead and create a stream field where we're gonna have an image and some text. 23. Image and Text Block : All right, let's take a step back and not work with something so complicated. List blocks. They have, Ah, lot of parts, and it's pretty easy to extend it. As we've seen in the last three or so lessons, let's go back to a regular struck block and let's just add an image a maybe some image alignment options. We're going to reuse the link that we used in our created in the last video, and that's also add an image. So first, let's go ahead and open up our streams blocks dot pie file, and I'm going to collapse that entirely. Make that just one smaller, and it doesn't really matter where you want to put it. I guess I could put this at the very bottom. I know want to create a brand new class, and I'm just gonna called image and text block because I don't want to name these very creatively. I want to name these verbose Lee, so people know exactly what they're looking at. This is going to be a struck block, a regular struck block, and I know it's going to be a struck luck because it's going to have multiple values in here. So it's going to have an image image alignment. Let's give that section a title, some text and a link. Well, we know Link is already done. Link is equal to link. Now where does this come from? We created a class up here called Link. We used it in our card. We can now reuse us all over the place. It's a struck block, and it already has custom value called dot u R L. So now I can use that anywhere. So this one, when it comes to adding but in text selecting a wag tail page or an external page are copying and pasting a link to an external page. This is already done. That's three field in one line. That's nice and easy image. We've used this one already. Image chooser, block, and we could give some help. Text on this one. This is going to be required as well. So let's say the image will be auto magically cropped to. This is going to be a larger image. So I believe it's 7 68 No, I said that wrong. 7 86 60 It seems weird. 7 86 by 552 pixels. If I remember from the design correctly image alignment, this is going to be a new one. This is going to be blocks dot choice block now. A choice block looks pretty interesting. So we've got choices in here, and this is going to be a couple of doubles, basically. So you've got choices we can put left and left, and I'll tell you what that means in sec with the left, one on the right, one means right and right, and that was very confusing What I said. So each trouble has two values in it. The left one is what we're going to have access to inner template and the right one is what's going to be displayed so we could say image to the left. Totti left to the left and image to the right. That makes it a little clear. We can also give it a default value, so the default is going to be left. Let's say the image is always to the left, and like all of our other ones, we can also give us some help. Text in here. We can also split this onto new lines as well. So it looks a little more consistent. Help Texas equal to image on the left with text on the right or image on the right with text on the left. Our title is going to be blocks Tut char block. We've seen this one before. Let's give this a max length of something like 60 characters and help text max length of 60 characters. Text is equal to block stud. You want to HR Block Yes, is not going to be very long. Text. So HR block with max length of what used to be a tweet. 140 characters, and we're gonna make this optional required is false and we've got a Lincoln here. Next, let's go ahead. Specify some meta options. Where's that template going to go? Let's say streams. Image and text block dot html and icon. I'm going to give it an image icon, and the label is going to be image and text with an N percent in the middle. So it doesn't say and it just says image ampersand text. Now that's all groovy. And hopefully I don't have any typos. Me terminal doesn't think so. I'm gonna copy this value image and text block just that text and I'm gonna open up home models that pie and in my body here, where I have the title stream field and the card stream field, I'm just gonna add image and text blocks dot image and text block. Now there's currently no template, but let's go ahead and see what this looks like in our back end. When we edit our page, someone wrong there block is not defined. Yep, that's correct. Block is, in fact, not defined its blocks. It's refresh our page, and when I click this plus, we now have image and text noticed the ampersand there that is the label. So now we can select an image, any image. It's like that one. Do we want image alignments to left or image alignment to the right? Let's the image is going to be to the left sample title in here. Sample texting here. Sample text in here. Link more details. Yeah, let's go with more details. An internal page Let's go with shining. In fact, let's change some of this. That's a title the cleanest rockets to ever exist, and the text is going to say no rocket has ever been this shiny. It's so shiny, it's almost frictionless. Go ahead and save that. Publish our page and again when we hit Refresh. This is going to look for a stream field template that does not exist. Yep, and it's looking for image and text block, and we know that is going to look in all these different places for it. So let's go ahead and make that exist under my ROCKETMAN project or whatever your project is going to be called or is called under templates under streams, let's create a new one in here. Image and text block dot html. No, this point. There's nothing in here we have not dealt with to the exception of image alignment. So let's tackle image alignment first. And then we will, uh, I guess we'll go through all the other ones and sort of feel this. I wouldn't make it look nice. Let's look at her image alignment. All we need to do for this It's a self dot image alignment, and that is going to match this text right here and again. When you're in a stream field, it could be value dot, image alignment or self. That image lineman So let's say aligning. Let's give this in H one. And when we refresh our page do, do, do, do do it's going to be aligning left now it has nothing to do with this section. Don't be confused by that. This is going to be its own section right here. This isn't a completely different a section that already exists. Okay, so what I'm gonna do here is I'm going to fade out. I'm going to right a bunch of html and then we're going to fill in the missing pieces together. Okay, So I wrote a bunch of HTML in here, and we're going to fill in the missing pieces. But let's take a look at what this looks at right now. So when I refresh my page, I have an image here that has a link. You can see that at the bottom left there got a title, some text and an optional button. Although currently it's hard coded, so it's not optional. It's definitely there every single time. So we need to do some some work here. We need toe, basically remove all the to do's and remove this placeholder image. So let's just work down this one at a time, and let's look at each of the to do's here. So CC I 80 image and text section. I want us to be alignment. So this is going to be self dot image alignment and this is flex box. So this is going to be left or right, and it just matches with the CSS. Next, let's do the picture. I know that there's an eight rough up here, but let's see the picture first. So we want image self dot What did we call it? Not just image. That's an easy one. We want to make sure that it always fills exactly 786 pixels by 552 pixels as I am G and I am going to replace that with image dot TRL. Uh, the altar needs to be replaced. Even if there is no all text in there, it's always good to at least have the old attributes in there. Let's go ahead and refresh this page. Something broke. Oh, isn't that weird? Invalid block tag on line 11 image. Did you forget to register? Load this Take. You're absolutely right. I did. Let's add wag tail images tags. And remember, every time we use the Wag Tail image tag, we always need to load. Wait till images underscore tags. Now let's refresh. Alright, alright, alright, that's looking not half bad. Okay, let's do the title text and the button. So the title is right here. We're going to dio self dot title. Presumably that's what I called it. Yep, title next one is going to be text. This one is optional. So because this is optional, if self dot text and if and in here, let's do self dot text look that the cleanest rockets to ever exist. No rocket has ever been this shiny. It's so shiny, it's almost frictionless. That's at a little to do in there. Now this one's going to be a little bit different. This is not going to be self dot link text. This is going to self doubt. Link dot link text link, text. There we go. And the reason for that is because link is equal to link. Well, this is another class, so you can sort of think of it as link is equal to, and then it's going to sort of go into like a subsection of itself and its got link text is equal to and sort of. If you think about it like this, it's link thought link text is equal to, and now we need to add our links in years. So let's find all of these. There should be a few of them in there. Yes, two of them. So this is always going to be so dot link dot u R l. And we're getting that one from our struck value. So we've got a link that we're using here link text, internal page, external link and is using a link value or a struck value. And it gives us the dot u R L value. So this is custom stream field logic. This doesn't actually exist in the stream field, but as soon as the stream field is being rendered onto the page, it does exist. So we have access to link that you are l Now, Lastly, we should make those links optional. And the reason we're going to make those optional is because we have internal page an external link, and they're both not required. They're both optional, so there is a way for someone to just ignore both of those fields and there won't actually be a link. So let's just go ahead and solve that problem right now with if self dot linked that you were gonna make sure that that exists and if and we want to do the same thing with our closing tag and if and that's clean that up, there's one more. Where are you? Right right there. You have to link exists. Show the link. And if let's go ahead, all right, so that is now going to link to our shining service. So on our home page, we've now got a banner and we've got a stream field here that says the cleanest rockets to ever exist with the link that goes to more details. And when we click it, it goes to our shining service. And that is how we create yet another stream field, a struck block stream field with an image chooser image alignment, which is technically a choice block title text. And we're already reusing the link components in two sections. So we're really making use of that 24. Radio Block: Oakley Doak. Early in her last video, we created image alignment with the choice block. Now, a choice block can be great if you have multiple choices. But if you only have two choices, sometimes you just want, like a radio select option where it's like you in select one or the other with little boxes . So in this video, I'm gonna show you how to do that. We're going to open up our block. Stop I. And in the last video we created, image alignment is equal to block starts Chooser block. We're actually going to replace that with more of, ah, radio chooser block. So I'm gonna write the code first. Then we're going to do some imports and then we are going to replace this. So let's go ahead and write class radio chooser block. I guess it's not really a chooser like a page chooser or I am in shoes. Or maybe let's not call that. Let's call it a radio select block blocks dot choice block and you can notice that this is the same as this, and now we're actually inheriting this directly. Now, if we wanted to, we could go on dissect this source code. We could right click and go to jump to definition. Or you can view the source code on Get Hub at get hub dot com such wag tail slash wag tail And in here we went. Choice is a little bit of ah in it in here, a bunch of stuff going on. Get culpable choices and you could spend little time in here. I just sort of going through the source code is actually not that long if you get rid of all of the dock strings and the comments. But if you wanted to, you could always explore that. I always encourage you to look at the source code, even if it doesn't make sense at first. It's good to just read over it, because every now and then something clicks. Let's do definite. We saw that function. So let's pass in the arts and the quarks. Let's run super on this so it already runs in it from Choice block. And let's also pass in any sort of our eggs and choirs that are going to be passed into there are in tow. This one. Ah, and then let's set a field widget if I remember this correctly. It is self dot field that widget is equal to forms. So this is a jangle form who were going to import this in just a second. I believe it's a radio select. And then we can say the choices are going to be whatever the self done field, that widget that choices are. Now if you don't understand all of us because maybe you're new to Django or maybe you don't know any jangle it all. Then honestly, don't feel like you have to go and learn all of this. And if you like this feature, don't forget you can just steal it, right For my source code, it's available to you. Now what we're going to do is import this. I'm also gonna fix up some of my type was there. You saw nothing. There were no typos. Right now I'm gonna scroll all the way up to my page and I'm going to do from Django import forms and I'm gonna go back down until where the forms are. And where are way instead of blocks? Start choice block. All I'm going to do is replace it with this text here. Radio Select Block Let's go ahead and change that to a radio select block. It is, at its core, a choice block. All we said was used a different field widget and so everything else that we're applying default, help, text and choices. They're all going to be applied already. Anyways, those are the key word are eggs that were passing into in it, which is running super, which is going to run super on block stock Choice block. So everything is still getting past upstream. Let's go ahead and save this. Quarks is not defined. Yes, because it's not choirs, its core gigs that looks healthier. Let's go ahead and refresh our page. And would you look at that image alignment image, the left image to the right image left image to the right, and nothing is going to change in your template. All we did was swap out the widgets that wait till was using for a radio select. So instead of a drop down, we said, use a radio select. If you like this kind of thing, definitely feel free to use it. If you think this is just totally useless in sort of a waste of your time as a developer, ignore it and let's move on to the next lesson 25. Call to Action Block: Let's look at creating another new stream field, a call to action stream field. So let's say someone's on our page and we're trying to sell them on our rocket maintenance services, and we want them to contact us. Well, currently, we scroll down the page. There's no real way for them to contact us. Eventually, we can create a contact patient. We will. We will definitely get there. But at this point in time, we just want to create some sort of call to Action Block. Now, this is just going to be a very basic title and link and literally nothing else. So let's go ahead and add that block. Now I'm going to I'm gonna close all this stuff and pretend that I'm starting from scratch . Let's go ahead. Open blocks slash. Nope. That's wrong. Let's go ahead and open streams slash block. Stop I And at the very bottom of my page, I'm going to add class. Call to action block, block start struck block and I'm just make some room to work there. Okay, so I said, there's going to be a title that's going to be something and a link, and that's going to be something. Well, we know the link is already set, so cool that's done. And the title is simply going to be a char block, and we're gonna give us a max length of maybe 200 so it can be fairly long. If we wanted it to be and help Texas going to say max length of 200 characters, let's go ahead and add Our meta class template is going to be streams slash Well, let's do this one a little differently. It's going to be looking for a call to action blocked out html. And in fact, let's just see what it's going to be looking for. Just as proof. Let's not give it a template. I am going to give it like an icon, though, and then I'm going to give it a label called Call to Action. Actually, now this is great. This does nothing, though, because we need to go over to our homepage and enable this So in her body, we just have to type. I don't know. C t a block start call to Action Block. And remember this Texas actually not being used by us right now, there is a way to loop through every single stream field and detect which stream feel it is , but we're not going to be doing that. Let's check out the terminal Terminal says There are no issues. That's good news. Let's go ahead and edit our home page and we should see a call to action blocking here. But Babu, there it is called Action, and the action is just going to be something in the long lines of the best dang rockets ever cause. I don't know. I guess we're making rockets now. Probably not. I'll change. I content a little bit later. Uh, let's I want to do this shining. No, let's not do shining. Let's do cleaning. And let's change us from the best dang rockets ever to actually something a little more applicable here. Was he a rocket in space recently? Get it cleaned. Clean your rocket now can imagine they get pretty dirty with all that fuel being being used and, uh, you know, entering and exiting the atmosphere and all that. All right, that's saved. Let's refresh your page. And would you look at that? It's actually not saying that the template doesn't exist. It's just rendering it straight to the page. No, that's cool and all. But we don't want that. We want this to actually have a proper stream field. We're going to say it has to live at called Action. Blocked out html. And when I refresh this page now, we're going to see that simply does not exist. Area We've seen this. Oh, my goodness. 100 times by now, let's go ahead and make that exist. Rocket Man Templates, streams and new file call to action block dot html. All right, so I just added my call to action Block in there, and you can actually see what I did. There s so there's a title and there's going to be a link. Actually, don't think that Link is quite right. Actually, that's supposed to be class. That looks better. Okay, now we just need to add our title again. This is coming from a stream field. So a stream field weaken yourself dot title or value dot title. Let's go ahead and do self dot title not to be confused with a page title. And because this one is a link that comes from called action, So self dot link link is going to give us link text, internal page, external link and through customs stream field logic You are l so in here we can dio self dot link. Don't you are? No, that's the wrong one. Link text. And for the girl, this is where we were doing self dot Link that you are Huzzah! Was your rocket and space recently? Get it cleaned. Clean your rocket now and it brings us to our rocket cleaning service page beauty. Now, this has been fun, but, uh, I think it's time to start getting into a few different types of stream fields and also playing with things like a snippet chooser block where we can actually select a testimonial . 26. Testimonials and Snippets: Once upon a time, many, many videos ago, we created this testimonial section. We had it quotes from Yoda and Chewbacca, and then we also registered are testimonial as a snippet, and this testimony was actually a jangle model. It has nothing to do with Wag Tail, except for the fact that we we registered it in the Wag Tell Edmon and registered it as a sniff it. Now snippets are nice because they are pieces of data such as this that we can select using a gooey graphic user interface. And in this video, that is exactly what we're going to do. We're going to allow people to select a testimonial and create a testimonial stream field. But instead of having to write the stream field over and over again because maybe the same , the same testimonial is going to be used on four different pages. We're simply going to say, Select one of these, and when we change it on one page, it will change it on every page. And let's just go straight to our home page home models that pie. And in here we are going to directly at a new stream field, and this one's going to be called a testimonial because we're going to be using a stream field that is just one particular type. For instance, we have this title block. It's a struck block and it only has the one field. So why are we using blocks that struck block when you know we could use blocks dot char block instead? Now, if we have more than one field, like in the card or our image and text block, even in our call to action Block? Sure, it makes sense to use a struck block, but when you only have the one field, it's sometimes easier to just not to use a struck block it all and use what Wait till gives us out of the box. So what we're going to do here is we're going to say our next dream field is called testimonial, and we're going to use a snippet chooser block, which does not come from our blocks file and at the top of our page here. We want to do from wag tail dot snippets dot blocks, import snippet, user block. And if we look at our terminal, you can see clearly it's complaining. We have one positional argument. Missing target model. It does not know which snippet to select. Now, even though our site really only has the one in here, we still need to tell which model to select. Now that's actually really easy to specify. Here we can simply say, right inside of the block, pass it in directly. We can say Target model is going to be. Which model do we want to use? Well, in a string, So we'll sort of lazy load this testimony eels. Can I spell array testimonials? Don't test Moughniyah Will. I have no idea if I spell that right. My terminal will know, though that looks OK. Let's go ahead and thats editor homepage Home page on page home page That scroll to the very bottom we have a new one in here called Testimonial automatically gives us the name and that name is simply coming from here. Let's make that just a tad smaller. That name is coming from here, so it is actually being used at this point because we're not giving it any sort of extra labeling. The icon is the snippet icon, just a couple of leaves that comes default and now it's going to tell us to choose a testimonial, and at this point, let's choose classic Chewbacca. Let's go ahead and publish, and when we load up our page, it's not quite going to be what we expect. We're gonna scroll down here and cool. We see the testimonial, but we can't actually make too much use of it at this point. So let's go ahead and pass us a custom template as well. And at this point, I think it's easier if we just put this on separate lines. So the first parameter, the first positional parameter keyword argument, rather is the target model. The 2nd 1 we're going to give it is a template, and this, at this point is no different. Then passing a template in the class meta. So we're simply going to say this temporary needs to live in streams slash yes, Imo nial block dot html And now when we refresh our page, we're going to see when it loads. By the way, if your side ever loads really slow like that and you're getting impatient with it, it's this thing right here, you Django debug tool bar. This is a profiling tool, so it's going to log all the queries, all the static files, everything in there. If that's taking too long, you can just go into your deft up high settings and disable it. So we're seeing the dreaded template Does not exist error. He's actually not dreaded. We know exactly what it's doing. Is trying to look for a testimonial block in all sorts of different places. Let's make it exist. So in our streams, let's go ahead and add a testimonial block dot html. And at this point, it's sort of difficult to understand what we have access to because inner c t a block We had a title, so it was self doubt title or in our text block. I believe it was just text. Yes, so inner in our template, we would have just said self dot text or value dot text. But what do we do in a testimonial block or a snippet chooser bloc? Rather when we don't know what's being given, So what we can do is start to experiment. First thing we can do is just pass in self. Let's see what this returns. You can see that it says order by Chewbacca. Well, that's not the testimonial. The testimonial is G R R R R and the attribution is Chewbacca. So if we open up testimonials, the models file, we'll actually see that we've got a quote here and the attribution. We also have underscore underscore str underscore underscore. So dunder stringed under And that's what it's returning is returning the string representation of itself. We don't want that. The nice thing about that is whenever you see an object returning the string representation of itself, that's because it's the entire object that's being returned. It just doesn't know if you want to see the quote or the attribution Fielder or what do you want to see? So it just says, Okay, well, I'm going to give you the name that you gave it And that's why. And that's exactly why we set this earlier was, uh for helping, but also makes it look a little nicer. So let's go ahead and try itself DOT quote. And when we refresh your page, there it is. There's are quote and attribution, and once more, I'll refresh your page and it will say Chewbacca just like that. All right, so I didn't want you to watch me, right? All this boring HTML test the fact that I made a couple typos, my CSS that didn't quite work out the right way the first time. So I faded out, faded in, and we're going to fill these in together. So let's take a look at what this now looks like with just the to do text in there. We've got a quote that we need to fill in and attribution that we need to fill in. So now that we know how to access this weaken, simply do self dot quote and down here we can do self dot attribution and again, I'm getting these directly from the snippet itself because we have a testimonial here. It's a model. It's a jangle model, but it's registered as a snippet and we didn't use a struck block. We said, Just select that piece of of content. Select that testimonial. Just that raw testimonial. This testimonial model now becomes quote unquote self or the value. So let's check this out. Oh, yeah, that's looking pretty good. Okay, We have one more thing here because I really want this. What's at an em dash? Start the long one. I believe That's a long one. Yeah, it is. That's Ah little dash there. And now we have a snippet chooser block, and at any point in time, we can changes testimonials. Let's do two examples here. Let's go and edit this testimonial. What else is to Bacchus say? Ah, he shoots a gun So it says P p p u you be you refresh. Notice how I didn't change the page. I changed the testimonial data. I did not change the page at all. And when I go back down to the page here, well, this is the old one because I loaded the pages roughly at the same time. If I choose another testimonial look at that. I can select, give people hope you by Ibaka or do or do not. There is no try Biota. So I mean, it's like that one publish. Refresh the page and this will change as well. Huzzah! Now we have two different ways to change data in our website. The nice thing about this is, if you wanted to, you could add this snippet chooser block to your flex page to your service page to a future blogged page of yours. And if you were to, I don't know accidentally make a typo in here. You wouldn't have to go and change out on four different pages. You could literally just go in here. Change it once, and it will change it across your entire site for you. And there we have it. We are now using a wag tail stream field to select jangle model data. That is pretty cool stuff. 27. Table StreamField: Let's take a look at table based stream fields. Now. If you come from a front end sort of developer role, you're gonna think, Why would we ever use tables? Tables are obsolete. Tables are the worst thing ever invented. There's literally nothing worse than a table when it comes to the Internet. And while most the time I would agree with you, there are some cases where you need to use the table. Sometimes you just want a table. I don't know. There are reasons for them. And ah, we are going to cover that in this course. So first things first we need to enable these, and I'm actually just going to reference the docks because I don't do this one too often. So I don't remember it off the top of my head, which is also great because it gets to show you that even people on the core team reference the docks. So I actually have this up already. Ah, here's the table lock and is going to create something like this. We'll get into that in a little bit. Ah, the biggest thing we have to do, though, is take the wait till contract table underscore block app and enable it. So let's open up our base by base based on pie and throw that in there. And I just put that with the other contracts. And then we can close that now to keep this somewhat presentable looking so that the code doesn't just look like a giant mess. I am going to sort of go about adding a table block in a roundabout way. But the shortcut that we took in the last video you can absolutely use as well. And if you don't recall what the shortcut was, definitely watched the video on the snippet testimonial block because we added a snippet without doing anything in our block. Stop, I So what I'm going to do here is I need to do an import. Actually, the 1st 1 here from wait till the contract table, block dot blocks import table block. Okay, that's throw that at the top of our page and go all the way back down to the bottom. I'm going to take a class. What do we want to call this? A pricing block, Maybe a pricing table block, I suppose. Let's say there is a table block here doctoring of I don't know, pricing table block. I don't know if you're going to use a pricing table block or not. But, hey, you know what? Let's add one anyways. That's at a class meta template is equal to. So we know which template to you is pricing table block dot html And I'm actually going to disable that just for the time being. The label is going to be a pricing table, I guess, because that's what we're calling it. Icahn is going to be a table table and help text will be. You were pricing tables should always have four columns or some other advice like that. So no, we can grab this. Go to our home models that pie by grabbing this cause you can't see that. I'm pointing with my eyes but pricing table block and we're going to add it to our home page pricing table. Sure, and let's add block start pricing table block and that is it Checker Terminal. There are no complaints. That's good news, and thats editor home page Now we probably don't want to pricing block on our home page pricing table, but should we ever wanted, we could put it there. We could put it on any other page as well, which we're going to do in a later video. So I just selected our table, and by default we yet is the Roa Header is the column header. So, like, should these be bold? And should these be bold? And the table caption is mostly for screen readers. It helps screen readers with accessibility so that people who have a hard time seeing what's on your website understand what the table is about. It just gives those devices a little more context. It's really helpful. So let's say rocket package pricing table. That's what we're gonna call this. And I want the first row here to be a header. So it should. Hopefully, people, if it's not, will make it bold. And we're going to have ah standard standard package, standard package and a premium package. We've rocket cleaning standard package is gonna have a check box. Let's go ahead and add a check box. That guy right there and also comes with premium. But what else? Rocket cleaning rocket shining is a premium package only. Oh, no, I'm out arose. Okay, right click Insert row below rocket fueling. If you get our premium package well, even fuel your rocket for you. Ah, answer. One more row below rocket painting. We will repaint your rocket in case it lost some paint when it was entering the atmosphere . That's good enough for now. Let's go ahead and publish this click view Life going all the way down and we can see that this is a table of sorts. And sure enough, in the source code, it is, in fact, a table. Now what we need to do is we need to go back, enable our template. Now, the problem with this is we don't know where to get this source code from. So what are we possibly going to put in this template? Well, we have really only one way to do this. We need to get to that code from the source code. So I am going to right click on my table blocking, go to definition. And I'm just gonna skin in here for where is the template? It's got to be in here somewhere. There it is. At the very bottom. It's already setting a template table block, blocks table dot html. Now, can I open that in here. I cannot open that in here because I don't have a quick little jump like that. So here's what I'm going to dio get hub dot com slash way tell slash wag tail and I am going to look for table block blocks table dot html. So it's just going here tabo dot html When searching this repository and this looks right, Wait till Con Trib table block. We did import that Templates. Table block, blocks table dot HTM own. There it is. And here is the source code that we can use. So what I'm going to do is copy this whole thing into my new template. We can close this one into my new templates called Pricing Table Block. I haven't created that yet, though, so let's go ahead and create that in the Streams folder. Let's do praising table block dot html and throw that in there. We could even had a fake little title. Just we know that this is custom refresher page. Sure enough, this is custom. Now, at this point in time, you can go through this and you can say that the table head should in fact, have I don't know a minimum with of 300 pixels or something like that. You can also give the table class. They're the table elements, a table class of table, responsive. Just play block things like that and you can customize this to be exactly what you need it to be. Now I'm just going to copy what I have pre made for this one. There's not really a lot of customization in this. To be totally honest, I've really just made this look a little nicer with bootstrap classes so you can see that Ah, my codes a little bit different. I actually did at a minimum with of 300 pixels for the first column and for every call for every other column, there's a minimum width of 170 pixels. Now, let's go ahead and refresh this page and see what we get. We can see at the bottom here. Ah, I actually did an interesting thing here where I want actually pricing at the bottom, but that may not be what you want. So I'm gonna look for that. And I just happen to know that I've added this in here is Footer. I don't want that. Let's get rid of it. That's just trying to make this column in this column Bold at the very bottom. We don't want that. That looks better. And now we can actually go into responsive mode here. Where is that I got there? It is responsive mode due to do to do that. A card. We've got a table here that goes all the way across. So look at that responsive table. Now, the next thing to note is that we can add custom settings. So we've got a default configuration, but we can add some extra stuff. We can change the default in here if we wanted Teoh. So minimum spare. Rose, How many rows you start with? How many columns do you start? With things like that. I am not necessarily going to apply anything custom, but I'm gonna show you how to do this. I'm gonna grab all of this from the docks. And where am I? In the home page. I'm going to want to pass table. Options is equal to new table options now. New table options does not exist yet. We're going to make this variable exist in just a moment. Something to scroll up here, create a new one called New Table Options is equal to, and it's really just a dictionary. The minimum Spare Rosa zero. I don't know. Starting Rose is gonna be four starting columns going before so, instead of a three by three, will have ah, four by four column headers. Yes, we have column headers, row headers. Actually, it's too. Column headers. False, but Row Headers, True Context Menu. We can change a bunch of this stuff in here as well. Editor text. That's the one we want. I'll scared of language. The height, renderers. There's all sorts of stuff in here. We would actually be in this video together for quite a while if we went through all of the different options. But definitely check out the docks and also check out the I think it's called Hansen Table . Yeah, Hansen Table 6.2. Here there are different settings that you can check out in the documentation as well, but let's go ahead and see what my new settings look like, so we're not going to see in here because this table is already set. But let's go ahead and add a new pricing table. Look at that. We've got a four by four grid inside of a three by three grid. So there's some customize ability in there that weekend. Weaken Dio. Really nice thing about this is if you have a client, whoever wants to just, like, display stuff in a table. But it's already in a spreadsheet you could just copy. Like from Google Sheets, I believe. Works pretty well. You could just copy all the cells and literally just paste it in there. And they just work really nice. But I'm gonna delete that one, and I'm going to save the page and we will be done with custom tables. So that is how we make tables and sort of work with changing the defaults table options. 28. RichText StreamField: Let's now add our first stream field to our flex page. After all, we've been adding all of these stream fields to her home page, and we're making it very flexible, which is nice. But it's not really the purpose of the home page purpose of the Flex Pages to be super flexible. So let's just go ahead, close up our code here and go to flex models dot pie and we can see Yeah, there's nothing in here, not a single custom field. Now, if I split my screen here and over here I go to home models dot pie, we can see oh, search of stuff. Let's get rid of that. And we basically want from our home page, this entire body section so we can literally just copy this over. Or if you want the practice, you can write it by hand. That's totally cool. It's actually probably a better way of doing it. And so now we're Flex Page has this body is equal to stream fielded, have title cards, image and text called the action, a testimonial snippet, chooser block and a pricing table. Now that's great, but the body is not going to show up at all. We actually need the panels. The panels are important. These content panels actually allow us to expose the body field. Because again, Wag Tail and Django, they make no assumptions about what kind of data we want to actually have accessible to our client or two. The content entry people. So let's do content panels is equal to content panels. Plus, and then we want to add a stream field panel and at her body. Now, if we're to save this page and we Yep, sure enough, we check out our terminal. It's gonna say Stream Field is not defined. Basically, this file says, What's a stream field? So we go up here and we do from rightto dot core dot fields import a stream field, and that's also do from Quito dot admin dot edit handlers import a stream field panel. Let's go ahead and save that. Blocks is not defined. Well, that makes sense. We have all sorts of blocks in here. We're using our own custom. Blocks were also using a snippet chooser block, so we need to import those as well. That's import our block, so it's do from streams, import blocks and Let's also import the stupid chooser panel from way tail dot snippets dot blocks import import snippet, chooser block. What else are we missing? New table options is not defined because new table options is on the home page. We could import that as well if we wanted to. We could do from home dot mottoes import new table options. And there we go. Now, if we actually go in, edits one of these pages. Sure enough. Yeah, we saw that air. We're going to see it again, as expected. Wait Till is now looking up our body field, all these stream fields and doesn't exist. So now I'm gonna cancel my server, and I'm going to make migrations. But I'm gonna run it just on one particular application. So I'm gonna do python managed up. I make migrations. And it's the flex app that I'm working on. Hadfield bodied Flex page That's looking good. Python managed a pie migrate run server again And when we refresh, this will go away. Uh, no. We have title standard cards, image and tax called Action Pricing table. And all of these were going to work the exact same way we set up on the home page. So within just a few minutes, we are able to completely customize our flex page, and it will just work. We did the work once on our home slash models a que our our home page here we did the work once. Why would we want to do it again and again? And that's exactly what we're doing here is we tend to work once we're just going to leverage that work again. Now that's cool, but we want to be adding rich text. So let's go ahead and add some custom rich text here. We could get rid of this, and I'm gonna make a bigger again. At this point, rich text can be imported two different ways and I'll show you both ways. The first way is a very, very simple way. If you're just using just rich text and you don't need like, a special title field or like text alignment options or anything custom and you just really want a whiz e wig editor, this first way is definitely the way to go. So we're going to do from wag tail dot core import blocks, But we also have block, so you want to rename one of these. It doesn't really matter which one we rename, as long as we rename it consistently. So, like if we renamed our blocks, we have to rename this one and that one and that one and that one or an easier way because there's less of these weaken rename the wagged l one. So wag tail blocks is what I'm gonna call this. So import blocks as well. Tell blocks we can also put this on separate lines to make this, you know, a little nicer. Look at Let's go ahead and add Rich tax. Just a standard rich text field. We're gonna do a new stream field. It's the first value is going to be rich text again. It doesn't have to be rich text. That's just what I'm going to call it. And now I'm going to give this a rich text block. Save that and let's go ahead and refresh this page and we will see that I will now have an option for a complete with the wig. Look at this. It comes with bold italics. H two h three h four ordered list a Nord A list horizontal rules. You can even embed videos you can link to pretty much anything an internal link, external link, email link, phone link or an anchor. Lengthy anchor link is new to 2.7 documents, and you can even add an image in here. If you wanted to, I'd say this one's going to be aligned to the left. Now, Now, this is really, really cool because I can say this is an H three turned this into an H three put horizontal rule in here and I can say this, but none of her stream fields are going to show up. And again, that is, I actually forgot about that one. Been a little while since we worked on that. So it's open up our home page and remember this thing. Yeah, we just need that eso I'm literally just going to copy that. Open up my flex page, come down here and paste it. And so all we're saying is in the based on tht mail, there's a block called content. Whatever is in there, let's override it. Let's inject our stream fields into this now. This is going to work, not the way you would expect because include block doesn't exist. So we need to load wag tail core tags. Let us refresh our page. Block content appears more than once. Yes, because that is not a banner. For whatever reason, I thought that was a banner. Let's get rid of this. Let's get rid of that. We can only have one block content per page. How are Look at that. We got some rich Texan here. This will show up as and h three. Sure enough, there it is. This is regular paragraph. Sure enough, there it is. But what if we wanted to put this into its own template? Cause right now, this doesn't really fit in with the rest of what we're building. So let's go ahead. And let's tell this to use its own template template is equal to streams. And let's call this a simple, rich text block. And it's going to be called simple because eventually we're actually going to limit what features you can add in here. So we're going to get rid of the titles because there's already a title. We're gonna do a bunch of other stuff to it now that template does not exist. Let let's make it exist so simple. Rich text block dot html and that's going to go in these streams folder. And so I just wrote some HTML here. And when we review this page rich detectors, the rich text is going to go here. But, like, what do we put here? Well, let's go ahead and put self. Sure enough, we could do that. We've done it before now that's pretty great. Let's go ahead and edit this once more and let's put a link in there just just to really drive this home. Let's just grab a link and let's link Teoh. I doesn't really matter the about us page. It's just sample content and we just want that linked to show up. And sure enough, our link does show up as expected. Now, what if because while I'm editing this, what if I don't want images in here? What if I don't want people to link to documents and I don't want h 23 or four in there? Well, with a rich text block, we have this cool little keyword argument called features, and we can tell what features we want. So we've got bold italic, so let's type bold metallic Oh, and I just thought of a good joke that I am totally putting in here. My favorite font as an actor. Italic bold win. Ha! OK. Ah, we don't want h 23 or four, but we do want both the lists. So let's go ahead and put the list in here. Even ordered list. Even a Norden list. I don't want the horizontal rule. Get rid of that. I don't want anything else except links. So let's go ahead and put Link and let's refresh your page and let's see these options change up here. And would you look at that bold, italic ordered list on ordered list and a link? Plus, you know, the line break that always comes with a busy week can't really get rid of that. Undo and redo. And if it any point in time, you are thinking, Hey, Caleb, I mean, that's cool that you can limit features, but where did you get these from? How do you know to put bold or italic or oh al or you l or Link or anything like that in there? The docks. That's my That's my totally not awesome answer. I was just the docks. Eso if you go into the docks and you just look at customizing the Where are we? Hear advanced topics Customizing Wag Tail Customizing the editing interface. You can actually see that the feature identifiers provided on a default waittil insulation are as follows. H 12345 and six So you can actually enable H one, 45 and six. Bold italic. Oh well ul horizontal rule link a document link image and embed. There's also a few additional identifiers as well. They're not enabled by default, but you can enable them in your list so we could also put code superscript sub script strike and block quote in there as well. So that's option number one. Option number two is if you need toe use rich text in a regular stream field with some other option. Let's go ahead and add that so we can create essentially a new one in here. So rich text with I don't know that title on this. Give us a link. Two blocks done rich text with title block, and this currently does not exist. But if we open up streams dot blocks, we can add it in here class, whatever that was called that very long name and blocks dot struck block, and we can add a simple title like we have been doing before. Title is Eagle two blocks Tut HR block max length of maybe 50 or something like that. And then we could do the text or the It's not called text. What's called Content is equal. Do blocks dot a rich text block rich text block, and then we could give it features as well. So let's go give our page a quick little refresh here that'll get rid of this one for us because it's no longer available. We've got rich text with title. Give it a title. We've got Rich Texan here. We can limit these as well Features is equal to If we don't want any features, let's just give it an empty list. Lastly, let's give it a quick little template. Your template is equal to streams slash I'm going to reuse the simple rich text block dot html, and I'm gonna feel this with a title. Anything in here because I need a title hands through some warm ipsum in there and I must save a draft. So I can get a fresher version of this page. We can see now that all of this has been disabled. It's literally just paragraphs at this point. Paragraph, paragraph, paragraph, paragraph publish and let's refresh the page. So now we're going to see something that we haven't actually run into yet. We are seeing self as struck value with all this other stuff in it. Now the reason this is showing up is because in our template, we're simply saying self were saying Return the entire stream field itself when instead maybe we want a title. So self dot title and we want the content, self doubt, content and I didn't call it content. I called it context because that was a typo. But I'm gonna call it context in here just, you know, go with it. So we now have some context in there. Now, if at any point in time you ever see your page showing HTML or stuff that looks like HTML, you were going to need to use the rich text filter on it, so you load wayto core tags and you simply dio pipe rich text. Now this is already escaped and looks totally fine or un escaped rather. But sometimes when you're doing it this way, you might actually see, like, a link in here the actual HTML link or something that looks very similar to it. And you want that to be HTML. So you adjust to use the rich text filter, and that will solve your problem. No, I'm not going to keep any of this stuff in the source code. I'm gonna undo that. Close it, and I'm going to get rid of that. And I am going to get rid of this one because I'm going to stick with the original way we did it. And that is two different ways on how we can add a rich text block to our flex page stream fields. 29. Image Block StreamField: just like our last lesson. We are going to create a brand new stream field directly from a wag tail component without writing our own struck block. What I mean by that is if we open up flex models DuPuy from the last couple of lessons for roughly the last couple lessons, we used a snippet chooser block directly from wait till without using our stream slash block stop, I file in the last lesson, we added our own rich text. We gave it a template and limited the features and we didn't to use the block stop I file at all. Well, we did once and then I undid all that that work because we didn't want to keep that in this video. In this lesson on our flex page, I am on our flex page here. We're going to add a simple image chooser, and this is going to be a larger image. So let's call this large image and we're simply going to say from wag tail blocks. And if you're just tuning and now wait till blocks comes from from wagged Elle decor import blocks and then I renamed it to wait tell blocks Wait, Tell blocks, image chooser, block and in here I'm gonna give this a little bit of help texts, just like we normally would in a regular stream field. Going to say this image will be corrupt to somewhere around 1200 by 7 75 And let's also specify a specific template. Our template. So let's put this in streams large image block dot html. Now when I go back to our about US page, I broke something has no attributes, image, shoes or block. And that was because I was honestly going a little too faster. It's not from wag tail blocks. What was I thinking with that one? Images are its own beast in Wag Tail. It doesn't come from wait till blocks It comes from wag tail dot images di blocks import image chooser block Pretty much the exact same thing that snippet chooser block does. But this one is imaged chooser block. So let's go ahead. Save that our terminal is now very happy with what I have written Refresher Pidge and I have a large image block in here so I can add any image in here. I'll just add that one published and sure enough, When I preview this page, we get a template. Does not exist. Error. I'm not even going to look. I know that that's gonna happen. I'm going to create a new file in the streams. Templates. Subdirectory. So rocketman templates, streams. New file in here called Large Image Blocked out. Html. Now I can do my own custom rendition in here. I don't have to let wag tail control everything. I can do whatever I want at this point, but the first thing I want to do is I want to load the wag tail images tags because I want to be able to use the image template tag. And the image itself is this entire object. Everything that is being passed into the context of this template is the stream field itself. So previously we've done self dot image in a struck block, but this time there's no struck block. It's just an image we're going to do. Image Phil 1200 by 775 at his image. And then I can simply say I am Gee, that you are Ah, I am g dot lt And yeah, there. Is that there? Yeah, it's people felt that wrong wait till image is plural. There we go. And look at that. It's Ah, fairly big image, isn't it? Now that doesn't quite line up with the rest of our site. Looks like something is a little bit off there, so I I'm going to add a little bit of template ing in here. And then Caleb do the fast forward thing again. All right? So I just added that in there and to do to do, to do. There we go. That's looking a little better. That feels like things are lining up, right? Oh, yeah, that's that's better. Now let's make this page actually look like a thing like an actual paid, You know, Let's have some text. So we've got rich text in here. Let's go ahead and add some lower, um, classical or um above classic Lauren below, and that is looking just a little bit squishy with the image. Let's go ahead and add margin on the Y access. There we go. That's looking pretty good. So, you know, basic about us page, and that's how we use a large image stream field directly. Now, if you're coming from that previous video and you're wondering how we do it any other way. Well, we actually have several examples of this already. In our blocks up I image chooser is somewhere in here. Image in text, block images equal to image chooser block so we can just look at the image and text block dot html template as a reference for how to do it this way. Otherwise, we can just use the image shoes or block directly, and we don't have to worry about sub costing a struck block in order to get an image chooser block. We can just use it directly, which is really, really nice. You can also do the same thing with we've seen this before. Sort of blocs dot hr block. You can do it with blocks dot text block or block stuff page chooser block, and I actually I would like to encourage you to try that out, create a stream field and I don't know, call it custom page. Maybe don't call it page itself. That's a word that's already used in the in the template context. You might run into a problem with that, but custom page wouldn't be a problem, and then you could use blocks I guess would be Weg toe block, stunt page, chooser block. And you could then work your way from there. So maybe go ahead, give that a shot, see how that turns out. And that will give you a think a pretty good idea of how you can sort of bypass using a struck block altogether if you keep your stream fields nice and simple. 30. Custom StreamField Validation: let's add custom stream field validation when it comes to our link block, and I will actually just pull that up when it comes to our link block that we have here. We have an internal page and an external link, and we've added a struck value right up here. The struck value to figure out which one supposed to come first and give us the UL property inside of our template. So basically, we've applied some additional logic to our link block. But because both of these fields are not required, they can both be empty and they can both be selected. And so, for your content entry people. When they're given two options, they can select an internal waittil page with a page chooser block. Or they can enter an external link, which is really any link to anywhere else on the Internet. They don't really know which one's going to come first all the time. You can give them help text on both of these fields, and yes, that's very helpful. But it's even more helpful If you Congar I'd them programmatically. Now what I mean by that is, if we added our home page, there should be a link in here somewhere. Sure enough, it's our very 1st 1 We've got a Lincoln here with link text Clean your rocket now internal page. And there is no external link. But I can if I wanted to provide an external link. Wait till dot Io And this allows me to save this Totally fine. There's no problems with this now. That in itself is the problem is there are no errors because when I come back and actually preview the page, clean your rocket Now you can see at the bottom left that is still brings me to the cleaning services page. However, I also added the way till dot io link in there. And when you're working with your clients, you want to make content entry the easiest thing for them cause honestly, maintaining ah website with good content can be quite hard and so a good way around this is to simply solve this problem by giving them some form of errors and so we're going to give them validation. Now, the first thing we want to do and I'm gonna try to do this somewhat, all in one view here, So I'm going to do from django dot core dot exceptions import validation air and I'm also going to do from django dot core No, not DOC or that was habit dot forms dot you tills import ever list. And whenever this link is saved, we can run a particular method on here called Clean. So we can say Death Clean is going to take itself and a value. And let's say there are currently no errors. This is going to be a dictionary of errors, but currently there are none. And then we can say something along the lines of If you know, whatever fails, we don't know whatever is. We'll change that in just a moment. We can see errors is equal to whatever the field name is is equal to error list and then your error text here. And then we can say if there are, in fact errors in the dictionary. So if there are items in your dictionary, we can raise a validation error validation validation ever in your link and let's pass it some parameters. The Param zehr going to be the errors. Otherwise, if there are no other heirs, that's not going to execute and weaken simply run super that clean value now to quickly go over that just sort of as a quick summary whenever this gets saved, whenever a link get saved and this could happen multiple times. If you have multiple streams, fields using multiple links, this clean function is going to execute. And then we're saying for this particular cleaning, a sign errors to be an empty dictionary. And then if our validation passes or fails, depending on what we're trying to check, we can say errors. Whatever that field name is is now part of an error list. We imported air list and validation ever up here. Then we say, Hey, if there are errors because we can do multiple if statements in here, if there are errors, validation error is going to be thrown or is going to be raised, then we're going to say if there are errors, raise a validation air with some sort of text in there, and the parameters were going to be whatever that error dictionary is going to be. Otherwise, if there are no heirs, just keep doing your regular thing. That's what this means. Just keep chugging along, so let's go ahead and give this a quick little example? We can say if true, the field name is going to be, let's say, external link and the error text or the ever list is going to be not a really error, but it shows up anyway. Let's go ahead and save that. And there's no module name jango that form and that is actually correct. It is django dot forms plural. That looks better. Okay, so we're on our page. We have an external link and an Internal Inc We know that no matter what happens, this is going to run. And we're saying, if it's true, well, true is always true. So this is going to execute. We're going to then add external errors to our ever dictionary. It's a airless. This will then also be true, and it will raise a validation air. So let's go ahead and save our draft. And sure enough, this page could not be saved due to validation errors and check this out. Not a real air, but it shows up anyway. But Lincolns also using multiple places there. It is as well, and there it is again and one more. There it is again, and I know there's four validation errors because there's a little four up here now. That's actually not a very good example, because you don't want to run or raise a validation air every time someone saves a page, because then they won't ever be able to save their page. So let's go ahead and get some of the values from this whole block that's being saved so we can say the internal page internal page is going to be value. Don't get Internal page, and the external link is going to be the external link. And so at this point, we have two variables that we can use. We can say if Internal page and external link let's go ahead and raise two errors, one for internal page one for external link. And let's say both of these fields cannot be filled him. Please select or enter only one option. Something like that. All right, I'm gonna save that and let's go ahead, save a draft. We could just as easily publish a page as well, and this time we only have two validation airs. This is the one that were mostly working with, cause it's near the top of the page is most convenient to look at, but we can see that both of these fields cannot be filled. Please select or enter only one option, and it says it for both of those. Now that's fantastic. Let's do the opposite. Let's do Elliff, not internal page and not external link. We want different. There's and we want this error to say something along lines of you need just like at least one of these. So please select a page or enter a you Earl for one of these options. That's Checker Terminal Terminal. Life looks good and I'm going to get rid of that one, and I'm going to get rid of that one, and we'll see a different era this time. So just a show you that we can publish it instead of just saving a draft to get validation errors. Sure enough, here it is. Police. Select a page or enter a your AL. For one of these options, I am going to select the about US page. No, I know there's one more on here, so it's gonna throw me another validation air. However, this one past the about US one totally passed. That's not a problem. Where else are we looking? We've got one in here as well said. There always needs to be a link. We'll put that one as shining to do to do. Where is the other validation air? Is that it? No. Yet because there's two there. Let's click publish. Our page has been published. Look at that. They're page past our own validations. So now we know that this will always be an internal link or an external link. On top of that, we have additional logic already for our link value here, saying that if there is an internal page, use that one or if there is an external link, use that one and there will only ever be one of these. There will either be one or the other, but never both. And lastly, a little bit of cleanup. Here, Let's go up to the top of the page. Cool beans. Put those up there. Life looks good, and that is how we add custom stream field validation 31. Limiting Pages: up into this point, we've created a number of different pages, and we actually have one particular problem that we haven't really addressed yet. As our website grows, it can really start to get out of control. For instance, we can add a child page to our home page called Home. Now, why would we have to home pages on our website? We have a dedicated page just for home page. We probably don't need a second home page. There are some instances where you probably want that, but in this case, we don't on our home page. We already have a service listing page, and we are able to create another service listing page. We only want one of those, so that's a problem under our route. If you click that little icon up there, that globe and you say Add Child page, we can now say that any other page type except the home page because it's already available . But we can say that service listing page, the service page or the Flex miscellaneous page can also be ah, home page page type. We don't want that, so we need to restrict these. Luckily Wag Tail gives us the ability to do that very easily. So let's go ahead and start by restricting route. What child pages can route have now? We don't have access to the root page itself That's in the way tail package. But if I open up home models dot pie and I specify the apparent page types, I can give this a list of AP name with the model name APP named dot model, name, model name. There we go. Third Time's a charm, and this will say that the home page always have to have a parent of whatever app dot model name is now. This doesn't exist, but wag Tail core dot Page does exist. This is our route page so we can save this and let's let's go back here under our home page when we click. Add Child page Home page is no longer available home page. Can Onley ever live under our route page? So it's still here but is not allowed to live anywhere else. So now we're restricting how our websites created, so every website starts off with a root page. Then we've got a home page and we're saying there cannot be another homepage or If we go into our page, we cannot list a home page underneath our about US page. That option used to exist. It no longer does, which is really nice. So we're starting to limit some of this. Let's go back to a root page and let's get rid of these other options. Let's get rid of Flex Page Service, listing Page and the service page so none of these can live under the route page. Now. How we would go about doing this is if we open up are flex models dot pie. We can specify the apparent page types here so lets a parent page types is equal to, and this is just a list so the flex page can always live underneath our home page. That's okay. We already have that set up. That's where our about Page currently lives. But we also want flex pages to be Nesta vel underneath other flex pages so that you can create and about us and then maybe a team page and then maybe a dedicated page for each person at your company. So we're going to say the APP name is Flex. That's the current one we're we're in right now and flex page. So we're really just saying parent page types. You're allowed to be your own parent. Sounds a little bit weird, and the home page is allowed to be your parent. So let's say that, and I'm going to go just back a page here. Sure enough, this will allow me to create a flex page here. That's fine. I'm gonna go to root. I'm no longer allowed to create a flex page. So that's perfect. And just really make sure that this is working. Let's go and actually check out our flex page here. Let's add a child page, and sure enough, it's still allowed to be there. We want to eventually get rid of the other two, and there's a second page property that we can assign to get rid of that or get rid of the other two. Now we've got two more in a route that we don't want. We don't the service listing page or the service page, so let's go ahead and open up services models up high and her service listing page. That's what the parent page type this can only ever live underneath. The home page home dot home page again. That's the APP name, and this is the model name. And if we scroll down to our service page here, we could do the same thing parent page type. But this time we want the parent page type to always be the listing page. So service pages always, always, always, always have to live underneath our service listing page here. So we say services dot service listing page And again, that's just the app we're currently in it and the model, which is just this one up here. And I actually made a little bit of a type of here. It's not parent page type. It is parent page types because it's a list that can be more than one. So if you're screaming at your screen saying, Hey, Caleb, you made a typo Good catch. I sure definitely made a typo there, so I'm just gonna go back to route at a child page, and it automatically says, Hey, I only have one page type that's allowed to live under route. It's a homepage, so it automatically takes me here. All right, so there's another way to go about doing this. We actually solved all of our problems with this one method. The other method is the sub page types. So if we click add child page to our service page, let's go back to our service listing page here. And let's just comment this out and I'm just gonna hit back because I want to see all of these different options. So I'm at my home page. I go into here, I have a service listing page that's the page type here, and I can add a child page, and I can add a service listing page or a service page. Now we solve this problem by adding parent page type is the homepage, But we commented that out. So now we're saying the service listing page can also live under service. Listening Pitch can also live under a service listing page. We don't want that at all. The other method we can do here is we can say sub page types is equal to, and we're saying what page types can we set? And we're saying what page types, what child pages are allowed to live underneath our service listing page, and for this we're just going to say services dot service page and this will get rid of every other option except the one we specify here. This is also a list. If we wanted this to possibly live anywhere else or have a child pages anywhere else, we could set that as well. As long as it does not conflict with the parent page types that are set now that's not applicable in our current situation. So I'm just going to save this. And when I refresh this page, service listing Page is not going to be there. And it will automatically assume that I'm trying to create a service page just like that and let's go check out our tree. We've got a service page here and we can add another service page. Oh, would you look at that? Actually, we can add a service listing page to our service page. We can go Route Home page service listing service, detail, service, listing, service, detail, service listing, service detail. We don't want that either. So let's first of all, we cannon comment that which will solve that problem. But in her service page model, we can also say some page types is equal to, and let's say we don't want any sub page types at all. We're just going to provide an empty list here. That's it. Go ahead and save that. Go back a page. And now we can't even create a child page underneath our service pages. That's perfect. That's where I retreat is going to end. So we've got a home page. That's this one here. We've got a listing page. That's one we're currently listening. All the child pages on Earth, Children pages and then we have the child pages themselves. That's the service detail page of Service Page, and it cannot go any further. So we've effectively said that's as far as this can go down this route. So now we've set up a proper structure for where our pages can live. What pages can be their parents? What pages can be their Children? But we have another problem. Let's say our site on Lee should ever have one service listing page and we say and child page to our home page. We can have as many flex pages is we want. That's what we want. We want as many as the customer of the client or the content entry people want to create, but we don't want to service listing pages. That's confusing for content entry. So let's try and get rid of this one. Now. We do this a slightly different way because we've already specified the parent page types. We've already specified the page types that the child pages can be. We're simply going to say Max Count is one. There can only ever be one service listing page ever in this website. So now I'm gonna go back at a child page, and this automatically assumes that I'm going to be creating a flexor miscellaneous page because there are no other options I can no longer create to service listing pages. There's one more thing we have to do here in our route. We can create another home page if we wanted to. I think we should limit that because this is not a multi site installation of Wag Tail. This is a single site insulation of wagged, also with Wag tail. In Django, you have multiple websites running off of one installation of Django or wait till, but for this case, I just want to only ever be able to create one home page underneath route. So let's go ahead and open up home models dot pie, and we're going to say Max Count is eagle toe one, and that will limit it to only ever have one home page in the entire website. We can also, at the same time, do some page types is equal to, and we know the home page is only ever going to have two different page types. Now this is already taken care of due to the parent page types that we've set. But we can restrict this even further by saying the sub page types can only ever be a flex page that's already an accepted rule and services dot service listing page. And just as a reminder, that's the name of our app, and this is the name of our model. So that's being saved. And when I go to add another page to root, it doesn't give us any options at all. And that is how we go about limiting our pages. Now you might be thinking, this is useless. I don't want to do that, and you're going to be the best judge for your website. Whether that's good for you, your client, your content, entry people, that's a judgment call that you're going to have to make. But I personally like setting this so that when my clients come onto a website like this and they want to create another page in here, another listing page, Well, they can't. That's going to break some of the logic that we have built into this website already. Or instead of being able to create another service page underneath a service page and just going on forever, they can only ever create service pages underneath the listing page and again that helps maintain our logic as developers in the code. So I think this is Ah, really powerful set of features or a trio of features. If you don't think this is useful, you don't have to implement this. This is all optional. Otherwise, if you like it, feel free to use it. It comes baked in with Wag Tail 32. Navigation Menus : Our website is coming together quite nicely, but there's still no real navigation. We have fake links up here that don't actually go anywhere. Navigation is absolutely vital for a website. We just haven't built it yet. So we're going to start by creating new app called menus with the standard python managed up high start up menus. So open up your terminal and let's run Python managed up high start up menus, and I'm just going to rerun my server there. And when I opened up my editor, I'm going to see to close this stuff down. I will have a new folder in here called Menus. Now, we're not going to need views, so I'm gonna get rid of that. We're not going to need tests, so I'm gonna get rid of that. We're not going to need admin. We're gonna get rid of that. So I just have migrations in it. APS and models, not pie. Now, in our models up high file, we're going to end up creating a menu item on the menu class. Okay, So first, we're going to need to create a new menu, a cholesterol model. We haven't worked with cholesterol models But this essentially allows us to save multiple items at once without other objects being created or needing to be created beforehand so they can sort of all save at the same time. Now the reason for that is because a menu when there's probably only one menu, but it's going to have a menu item, another menu item and another menu item, and we want all of these to basically save at the same time. We also want these to be reorder a ble, so we're going to use a thing called an order arable. So I'm gonna clean that up, and the first thing I'm going to add is a class called Menu, and this is going to be a cluster herbal model, and we have an imported cholesterol model. This comes from the package called Model Clusters. Let's do from model cluster dot models import cluster arable model, and now we can use it. Next. We want our menu to have some sort of titles that we can identify it, and we also wanted to have some sort of slug so that we can easily identify it in our template so that when we tried to access this menu from, let's Say, a template tag, we could just pass in a particular slug. Basically, it's a text based I D, and we'll be able to find it pretty easily. So let's go ahead and do. Title is equal to models dot char field Max Learning thing. Let's just give us a max length of 100 characters, and the slug is going to be something called an auto Slugs. Fields. Let's do an auto slug field, and we haven't used this yet, but we will get into this in just a second. So let's say this auto slug Field needs to populate from the title. And what this is saying is, if there isn't a slug, grab the title slug If I it and use that, we're also going to say that this is edible now. Auto slug Field comes from another package, different one, and you don't have to use auto slug field if you don't want to. You can simply use models dodged char field if you like. I like using the auto slug field because it allows me toe be a little bit lazier when doing content entry, and I could just specify the title hit Enter, save the Model or Save the menu, and it will create one for me if I wanted to. Eso let's go ahead and use the auto slug field. This is going to come from Django extensions, which I don't believe we actually have installed right now. Django extensions dot tv dot fields Import auto, slug field and let's see what a terminal saying. It's saying absolutely nothing, and that is because this is a brand new application. We need to add it to our new APS. So in our based on pie, we're going to add menus. That's just the folder name. That's basically what I just call it, the whole lapses called menus. So it's going to match that close that let's refresh our terminal. There we go. This looks healthier. No model named Jango extensions. So we're gonna wanna cancel this Do Pip install Django extensions, Cool beans. Let's rerun our server. It's not complaining anymore, and let's continue on here. So we're not going to run our migrations yet. We're going to add a little extra stuff here, so because we're going to end up registering this with wag tails, model it, man, we need to give this panels if we give it panels. That means that the fields that were telling it to we'll be exposed so we can say, Hey, give it a panel for a title and give it a panel for the slug and those will exist. They will become edit Herbal. You'll be able to actually change the title or add the title and change or add a slug. So it's at a field panel here. This one's going to be title, and let's add one more field panel, and this one's going to be called Slug field panels also not imported. So let's do that. So we're going to do from my tail dot admin dot edit handlers import field panel checks terminal I don't have any type of is presumably, and we're almost done with this part. So next, when we eventually create a migration for this is going to create it a class called Menu in the database effectively. And whenever we use this, it doesn't have a name to call itself, so it's probably going to give it the name of the memory that's allocated. So, like sometimes in python code, you'll see something that's like, Ah, you know, something like that sort of looks something along those lines. If you've seen something like that, that's a little bit of memory that's allocated in your computer. So instead of saying that, that's the name of it, we're going to say we are going to overwrite this with a strings, a return self title. And now we're saying Every time we access a menu, object is going to be called Whatever the title is now, this point, let's cancel our server and let's do python manage dot pie. Make migrations cool. I have some migrations from the last video that we did with some flex page stuff, and I have created a new model for the menu that's your python managed up high migrate. Everything was applied all right, the three runner server, and we're not going to see anything in our advent yet. We haven't registered anything in here, so we're not going to see anything going one step beyond that. We also only have a menu. We don't have menu items, so let's go ahead and adds a menu items Now. Now this is where it's going to get a little trickier to understand at this point, this is You can think of this just as a regular model and as a title has a slug, and eventually we're going to create some menu items and we're going to relate it back to this menu. So I'm gonna create a class called menu item and this is going to be an order. A bowl. Now we don't have order Bols imported yet. So let's do from wayto decor that models import or durable. Now, what kind of features do we want our menu toe Have we want this toe have some sort of link title? We needed to have some sort of link you where l and let's say this is going to be an external link. Ah, we're going to create a link page. This is going to be an internal link and maybe an open in a new tab option, and that's going to be a Boolean. So essentially what we're going to do is for every link item in here, it's going to have some text. It's going to have an internal and external page and the option to be opened in a new tab or not. So that's a little bit of pseudo coding. Let's go ahead and actually add these in here. So let's add models. Char field Blank is equal to true. No is equal to true. Actually, we don't need to add no is equal to true, because Blank is equal to true. Plus, it's a char field, but let's Adam max length of 50. The link you well is going to be an external ing, so this is going to be typically, we would say, a ul field. But this could also be an egg anchor link down the page somewhere. So it could just be a link that looks a lot like this bottom or contact and you'll scroll you down the page. So maybe that's what we want. So we're going to leave this open with HR Field. Give that on max length of 500 because external links to be pretty long, and it can also be blank with our internal page. Let's go ahead and create a foreign key to any wag tail page on our site, so we're gonna say models out foreign key. Wait till core dot page they were saying. Anyway, tell Paige and every single one of our pages is inheriting that regular page. So if we say we check out home, our home page is actually importing this page. And if we come up here, we're going to see that is coming from Wait till core models Page right Don't core dot models page make decor Being the AP page being the model no is equal to true. Yes, there's gonna be no blank is equal to true. Yes, this could be blank. That's optional. We're going to give us a related name as well, but we're not going to give it any special related name. We're not going to use that. So we're basically just going to discard that Related, named by using a plus sign and then on delete what happens when we delete whatever pages is being linked. Teoh. So if we linked to a contact page and we delete the contact page, what happens to this menu item? We're going to say this menu item gets destroyed by cascading and then lastly, we have open in new tab and this is simply a Boolean field, so a boolean field is a true or false default is equal to false and blank is equal to true so it could be optional does not need to be checked. And because again, we're going to be displaying this in our way. Telemann. Eventually, we will register it in the Wag telling men the model it man, we need to say, Hey, wagged Oh, we want you to actually show all of these. So let's go ahead and create a field panel for the Link title. We're going to create another field panel that's getting a little low there. We're going to create another field panel for the link. You are l that external link you are. L probably name that external link you Well, if we wanted Teoh, we're going to create a page chooser panel for the Link page. That's our internal page. If it's selected and we're going to create one more field panel for our Boolean field called Open in a new tab, we don't have our page chooser panel imported yet, so let's go ahead and import our page chooser panel. Now that's all well and good. But menu item and menu, other than being in the same file, have no relation to each other whatsoever. We need to create some sort of relationship. Now what I'm going to dio is I'm going to run migrations again and migrate them run server . And that's just going to actually create this menu item for me. Now I want to link this menu item to the menu itself, and we're going to see that could be done with a thing called a parental key. And it's going to link to the menu class, and it's going to have a related name we're actually going to use. This related name is going to be called menu items. Now let's go ahead and import parental key, and I can never remember where this one comes from. Eso You might actually see me make mistake here. I think for a model cluster dot fields Import perent. Uh ha. That's probably the first time I ever got that right on the first try. Parental key. So now that saying Hey this page or doesn't need to be called Page, it could literally called anything. It just needs to have some sort of connection to our menu here, so let's save that. Let's run migrations once more, and it's not model cluster field. I knew I was gonna get that wrong? I think it's model closer fields. Question Mark. There it is. Okay, let's go ahead. Make migrations. And now this is asking Hey, you're trying to add a non gullible field called page to menu item without a default, we can do that. The database needs to populate with something in there. Let's go ahead and select option number one, and I am simply going to type none. Go ahead and migrate that rerun her server. Okay, Things are looking a little better when they weren't looking bad anyways, but things were looking like they're starting to sort of shape up now. That's Ah, let's go ahead and actually register this with Wag Tail so we can actually access it in here. Now there's two ways we can do this. We can register this as a snippet or we could just register this in the menu of section here. I like registering it with the menu section. It just feels a little better. So beside models, not pie, I'm going to create a brand you file here called Wag Tail hooks dot pie and it absolutely needs to be called. Wait till underscore hooks dot pie. So in here. We're going to do two different things. We're going to import to the model Edmund, and we're also going to import the Decorator Deco rate. Or and then we're also going to create a new class. We're going to call it men, you admit, and it's going to be a model Inman, and we're going to register this. So let's go ahead with the 1st 1 and just do the import from Wait till dot con Trib motto Edmon dot options Import model Littmann. We're also going to import model admin register. We're going to then create that class inherit model Litman the White Tail model Lydman We're going to say, what what? Which model does this come from? What are we actually admitting here? We're going to say, register this menu item now We need to import that as well. So from dot models, important menu dot models comes from the exact same folder so we can just use dot models that just goes right over here. Import the menu class, the menu label that's got menus. The menu icon is going to be a list You l also looks sort of like a menu does need a comma there. The menu order. This is where it's going to be ordered in the menu here. So I'm gonna say 200 actually don't recall exactly where to hundreds going to place us. We'll find out at settings. Menu is equal to false. And that's saying we don't want it to show up in here and exclude from Explorer is equal to false. Okay, I'm just going to restart Django here just for good measure sometimes. SNC If it had restarted on its own, we wouldn't have seen that trailing calm has not allowed without surrounding parentheses. And that's because I hit. Enter should have just been on one line. It was on to on the last thing We need to register this model admin register, and we're just saying, Take this whole class model Inman and registered with Wag Tails Menu system. So let's go ahead. Let's check it. Wait till let's give this a refresh and wait for it to show up over here. Come on, There it is. Number two menus. Let's go ahead and add a menu. We're gonna call this menu header. You can see that it auto fills as I type. That's thanks to the auto slug field. I'm gonna call this one header and you'll notice that we have a menu in here. But we don't actually have menu links. We need to add that in. So let's go back to our model Stop high. And even though we have a parental key to our menu here with a related name of menu items, it's not actually being used in the database that it is being used. But we just don't have anything actually, in weg jail terms being exposed now because there could be multiple items, multiple menu items and because we are going to be using an order herbal, which allows us to move a menu item up or down. We want to use a thing called an in line panel, and this is going to take the related name. That's the menu items, and that just relates to this related name here. So it's the exact same text, and the label is going to be a single menu. Item in line panel is not imported, so let's import that terminal and is not complaining. This is good news. Let's go ahead, refresh this page and now we can add a menu item and another menu item and another menu item, and we can mix and match these in whichever order we want. Let's add a new menu item, and this is going to be a link to our services. And we're going to say that this is going to be a wag tail page when we're going to create another one in here about us and we're going to link to another way Detail page called about us save. And just like that, we have a menu system in place on the back end, but on the front end want mop? It still doesn't work because we have not implemented anything yet. And the next lesson we're going to create a template tag where we can actually go ahead and grab this menu from our template and loop through everything we need to loop through in order to create our menu up here, we're also going to create the foot or menu. Down here is going to be the exact same menu, so we'll use it in two different places. 33. Navigation Menu Templates : Okay, Dokey. In our last video, we created a menu system where we could actually go ahead and add a menu. We have multiple menus if we wanted to, but we created one called Header. And if we edit this, there are two menu items in here which we could move up or down, thanks to a thing called in Order Arable. However, we still don't have access to that in here. Now there's several different ways we can get access to this menu on every single page. One of those ways is through a template tag. We're going to take that route instead of creating a global object that's accessible on every single template. We're just going to use the template tag way. So we're going to create our first template tag. Now I'm going to go ahead close that we're going to create a new folder called Template Tags all one word, and then here we're going to create another new file or the first new file in there called Menu Tags dot pie and that name is going to be importa