3D Files with Wagtail
Published July 19, 2024
This video is from Wagtail Space US 2020 in Online.
Tim showed how a Philadelphia recovery-meeting site combined Wagtail and CodeRed CMS pages with a React meeting finder fed by one simple JSON endpoint, letting staff update meeting details and helping the site adapt quickly during COVID-19. Ryan presented a small Django tool that renders Markdown files as website pages through a custom template loader, while Vince demonstrated CodeRed CMS’s prebuilt page types and editing blocks for teams wanting more WordPress-like control. Paul explained using Factory Boy to build concise, reusable test data for Wagtail pages and relationships; Kate and Brian demonstrated a StreamField splitter that lets editors divide rich-text content in place to insert images or embeds. Across the examples, the speakers showed practical ways to extend Django and Wagtail with targeted tools rather than choosing between a conventional CMS and a fully headless build.
Summarised automatically from the transcript.
Automatically transcribed, so expect mistakes in names and technical terms.
Speaker 1: So last but not least we have our lightning talks and we've had uh five people sign up today. I've been posting the link fairly frequently to the point of uh people ridiculing me about it in the Slack channels. Um the five people who signed up are myself. Um Ryan Sullivan, Vince Salvino, Paul Smith, Brian Witten, and somebody is typing in right now. Tom Dyson. So it looks like we have six to go for today. Um I saw that one coming in real time. And uh I guess uh I guess I will go first and take my own medicine. Um So the the working title for this talk, and this may turn into a fall talk, it's somewhat of a follow-up on my WagCal
Speaker 1: talk last year, is called Failed Guillotines, Not Quite Headless. So some of you who were here last year will remember this website. This is uh a website I work on for a local recovery group here in Philadelphia. And it's come quite a bit of a way since last year. Um and the reason I'm calling this uh failed guillotines not quite headless is because we're using uh We're using Wagtail as our backend for an API for a React app, but we're not doing a single-page React app. We're using CodeRed CMS and all its capabilities.
Speaker 1: And then we are just doing a single endpoint without Django Rest framework, which feeds this entire app within the site. So if I were to surf around the site, you'll see most of this is standard. standard Django. So if I come to a history of AA in Philadelphia, you'll see this is a standard code-red CMS template that's set up just like any any normal CMS website would be. And the only React component we have in here is here on the front page where you'll see it loads. And you'll see this lists all the upcoming recovery meetings for AA in the Philadelphia area. We've got one touch buttons for joining by Zoom. or by phone because like everybody else, AA has completely changed over the past couple months
Speaker 1: and had to go online. Django and Wagtail gave us the dexterity we needed to be able to pivot to do this very quickly. We were able to turn around the site and have these kinds of features in within a day or two. of everything shutting down for COVID-19 because recovery meetings are absolutely necessary and vital to people in recovery and we couldn't allow a long turnaround time. So you'll see here it shows all the upcoming meetings where their traditional location was. Since these are marked as temporarily closed. You'll see that it automatically suppresses their address. So if I was to come in here and bring up temp closed meetings, you'll see that all of them have either a share or
Speaker 1: The address is crossed out. So how did this work on the back end? If we take a look here, you'll see We have a single API endpoint at slash meeting guide slash API, which contains a list of meetings. So each meeting structure Is a block of JSON, which is all pulled out of the back-end Wagtail database. So you can see here we've got multiple levels of regions, a latitude and longitude, formatted address. Everything that that front-end React app needs is right here. So all 2,000 meetings are presented right here. And then the React app grabs all 2,000 of the meetings. And paints the picture it needs to paint. You can see we have uh all the regions here in a hierarchy. So Bucks County has Berks County has two regions, Bucks County has many.
Speaker 1: You'll see you can choose the time you can choose what day you want. So the typical view is upcoming, but if you wanted to see what's going tomorrow, you could come to Friday. And it'll show you a list of all the Friday meetings. You can select what time of day you want in the evening. You can also switch over to a map Which isn't really relevant right now, but if you switch over to the map it will show you these are all the Friday evening meetings. So if I was to switch it to Friday anytime, you'll see a bunch more appear. And if I switch to the entire week, a whole lot appear. So on the back end, what we've done here is we've done a little bit of cheating of Wagtail So we have a meeting guide app that contains some of the metadata necessary.
Speaker 1: But within settings, if we come to sites, you'll see we actually have two sites. So locations. aaca. org doesn't actually It isn't actually an FQDN that gets rendered. It just gives us a second top level. So for the people who maintain this in the office, we used to have a WordPress backend, but they found it horribly confusing with too many options and this interface is much more straightforward to them so they can either update the website itself or the locations in the meetings And the pages under Wagtail are set up for locations and meetings such that meetings are children of the location. So if we come into a location here for Friends Meeting House, for example, and edit it, you'll see it's a location page Wherein you select the region, which is a tree structure, put in the postal code, you can attach any details about the location that you might want, like entrances in the back or anything like that
Speaker 1: And then you can do a fully coded geocoded address. So if I were to switch this, for example, to where we've had Wagtail space the past couple years, it automatically geocodes the address. And then I can even zoom in. And if I want to send them to the entrance at the other side of the building, I can drag this little Pin over here, and you'll see it actually recodes the Latin long to where I dragged the pen. That's a great little plugin called Wagtail GeoWidget if you've never seen it. And I'm not going to save that because uh it would actually change it on the site. But if we come into the locations and meetings and come to an actual meeting.
Speaker 1: You'll see all the meeting information is here as well. And here are the fields we had to add for Video conferencing, OneTouch conference phones, as well as Venmo and PayPal for contributions from members. So that was all added in real time. The upshot of this is we've given the office a backend that they are much more comfortable with. Since we've implemented this instead of the old WordPress with plug-in site, They are comfortably adding new events. They have comfortably gotten used to uh making meeting changes on a regular basis where it used to always fall to us as developers. So the Wagtail admin section has made things a lot more straightforward for them. And we did this with just a couple models. So if we come here you'll see we have a location page. And the implementation of the formatted address and the LADA and
Speaker 1: long and the region postal code is pretty straightforward. Any details you'll see using the geo panel is very easy, the Wagtail Geo widget. And then we have a meeting class, which is a child of the location. For those of you who are familiar with Wagtail, this should probably look pretty familiar. And then within the view, we don't even use Django Rest framework. We just build up in the get for the endpoint. We eager load all the region objects. And then loop through the meetings, build a dictionary with all the information we need, and then we could do this as a return JSON response. But we did an HTTP responsive content type application
Speaker 1: JSON Just quick and dirty, that's our single endpoint that's consumed by the React front end. Then since we're using code red CMS, it gives us all a bootstrap four out of the box. And all we did and I made this public, if you want starter CSS for code red CMS that doesn't look too bad, just copy and paste this into your style. And uh you'll be ready to go. So that is basically all the customization we had to do on the front end. And we've ended up with a pretty straightforward to use, functional website Which a lot of people have found incredibly useful throughout Philadelphia, especially during this difficult time. And if you want to see how it looks on mobile, it's pretty handy.
Speaker 1: That it converts down for mobile very nicely using React. The other major advantage to this is that the same API backend that we are using for the React front end is also consumed by a worldwide app. So any group like the Philadelphia group that uses this gets fed into a worldwide app. We have over 250 metropolitan areas. that are now being fed upstream into this app. So if you're in recovery anywhere in the world, you can open up the Meeting Guide app. It will geolocate and show you what meetings there are right net right near you as long as you're following this API format. So there's a WordPress plugin that follows this API format. There's now this Wagtail plugin that does. There's also a Drupal plugin or people can even use a Google Sheet
Speaker 1: So this has been a big win for us. And it's just uh you know we talk so much about headless wagtail versus regular wagtail And there's a whole you know set of shade of grays in between where you can go, where you could do, you know, pretty standard wagtail for all the accessibility ones and things that gives you, or using something like code red CMS out of the box. And then, you know, tack on a React app where it makes sense. So I just wanted to share that. Thanks a lot. All right, next up, I think we had Ryan Sullivan. Let me make sure I can make you a co-host so you can share your screen. Okay, good. Ryan is a co-host.
Speaker 2: Cool. That's great, Tim. Right. I'm going to give what is ideally the shortest lightning talk ever. So I had a challenge the other day. I had a something I wanted to do and I tried to do it and I couldn't figure out a fast way to do it. So I made an open source, I made a project, I made it, I did it, wrote the code myself, and I decided maybe I should make this open source. And then I realized once I made it open source that um that these boxes are red and that I don't know that I have um the time or
Speaker 2: the skill or a combination of the skill that allows the time to be efficient need to be do efficiently enough to have the time to turn these boxes green and to add more of these boxes, that is the build, coverage, and so forth So I don't know. I was just wanted to share this. This is really my the first project I've ever open sourced, and I just thought I'd share it with people since this has nothing to do with Wagtail other than that it's also Django. But um I thought I'd share it, and if anybody wants to give me a hand with some uh test covers and all that kind of stuff, I wouldn't say no. I also don't I'm not demanding or not demanding, but you know, uh I know everybody's busy is what I'm trying to say. So what I wanted to do is um
Speaker 2: So inside of my website, I just wanted to take the markdown and serve it. That was my only goal. And I said, how is like why why I have this markdown. I know markdown can be rendered as HTML. I just want to put it on the website, be done with it. And I m you know this might have been a for lack of of knowing how to do it or for lack of Googling Um but I you know I I Googled for a bit and I couldn't find a very a super efficient way to do it. Um so I made one I rolled my own. Again, not saying that I did it the right way. But basically what I did here is I said, well, let's make a view. And that view will just take a path to a template. And then uh you know
Speaker 2: create a path. I guess this is a ref spec. Give a path and then a name. And ideally this should be enough to serve a um to serve my markdown as HTML. So I wired it together. I made, you know, here are a couple of paths. These are all marked down. uh files. Here's one of my README files. And then here's just the documentation page. It's just the general basic template that I'll that I have some columns, this is all bootstrap, and um I'm just giving links to my you know to my URLs. Um and lo and behold it works. So now this is my same document page documentation page where I just have a whole bunch of links.
Speaker 2: And if I click on this main Django words readme. Here I'm looking at my markdown page rendered in on my website. So it's just on the fly turning this README into this page and to show that I'm it's not crazy Right, so that's it. And uh it's quite simple. All it is is a is a custom template loader. Um so It just it's this thing called Markdown View.
Speaker 2: It just creates its own template loader, um, does some fancy stuff with uh with Python markdown. renders the the template two or three times in order to get the images to actually work and uh and then serves it up. And uh and that's it. And uh here's the repo
Speaker 1: Thanks, Ryan. That was awesome. I just
Speaker 2: have to figure out how to stop sharing.
Speaker 1: It's all right, because uh Vince can take over.
Speaker 2: Thank you, Vince.
Speaker 1: You're up next, Vince Alvina.
Speaker 3: Hey, can you hear me?
Speaker 1: Yes.
Speaker 3: Okay, good. My uh zoom was acting up earlier. Uh let's try to share the screen. Okay. Okay, um right, so what I'm going to do is give you kind of the five-minute install of Code Red CMS, and this is um You know, basically what it is is we work with WordPress a lot and we work with clients who, you know, need to the smaller businesses, people who need to go in and edit their site, and it's like Wagtail is great because it's great for a developer.
Speaker 3: And it has a good user interface uh you know structure for uh front-end uh people, but uh or for uh editors, I should say. But the the downside of Wagtail is that it while it gives a ton of uh power to the developer, it really does not give any power at all to the user. which as developers is what we like. But the users are are looking for something where they can go in and change stuff and change colors and do whatnot without even having to ask their developer for help So it which is you know part of the reason WordPress is so popular. So um what what we're gonna do is I'm gonna show you the five-minute install. So uh first I'm just gonna make a virtual environment.
Speaker 3: Uh let me do this and then zoom in really big. Can everyone see that okay?
Speaker 2: Yeah, perfect.
Speaker 3: And this is gonna be totally live and I'm gonna try and hold it to five minutes. So that you can see uh see the whole thing in action. So well there's really no no coding involved other than the initial setup. So we're gonna do a pip install code red CMS This is totally open source. We have a GitHub page. You know, definitely open to pull requests and contributions and whatnot. It's still technically kind of in beta because we have a few uh a few structural issues we need to change before uh it will be totally stable. But um We are uh using it on a lot of different production sites.
Speaker 3: Uh it's really grown quite a lot in the past couple years. So This is um it's a great tool to have in your tool belt if you want to work in Wagtail. And uh I really liked Matt uh Matt Westcott's talk this morning about the extend versus the layers. Uh I think that's a perfect metaphor because uh We have kind of the uh extension of Wagtail. So you can you know you can still do pure Wagtail, you could still do pure Django. The only difference is you now have an extra set of features that you didn't have before at your disposal. So I sh guess I should have done this. I wanted to do a true live demo, but I guess my internet 's going really slow here
Speaker 3: because of uh all these electron apps and Slack and Zoom and everything. So Looks like we're about to be done here. There we go. Okay, so uh the the starting point is we're just going to do code red CMS start just like you would do Wagtail Start. Give it a project name. And that's about it. We're just going to enter that directory. This is pretty typical of a Django project We're gonna do uh migrate
Speaker 3: We're going to create a super user. Just let it prompt me. And then we're gonna run server. So that's about it. We now have our CodeRed CMS project running, and I'm going to go in my browser here And there's not much on the start screen, but we can go ahead and log in
Speaker 3: using our account that we created. And as you can see, this looks basically like an exact Wagtail site because it is. It's just pure Wagtail with a basically a ton of extensions added on. So normally with Wagtail you get a homepage, and with this you get a homepage as well. So I'm going to go in and edit this homepage. But one thing you're going to see is that there's quite a lot more on the homepage by default. So we provide a really beefy stream field, which is, you know, we're probably one of the worst abusers of the stream field. But um, you know, which is uh a lot of people have started using the stream field for a lot of things, which is why uh, you know, it's becoming uh kind of a hot
Speaker 3: hotter item, but we provide um some, you know, classifiers are like our uh sort of a categorization system that's built in. On every individual page you can change the actual template, the HTML template that gets used on the actual page object. Which is nice if you have like a sidebar page or a different, you know, a common example is one that shows the navbar or one that doesn't show the navbar or different things like that. We provide a bunch of SEO fields so you can get into the Google structured data. You can specify open graph images and all that kind of good stuff. These are we have do have content walls if you need to do pop-ups or age gates
Speaker 3: or any of those, you know, just a lot of stuff then and this is all like boilerplate stuff that we have to build for every site we do. So we just put it all into one package So I'm going to start out with the homepage. I'm going to create this hero unit. All this is built into the stream field. Let me add an image if I can find one. Here's a nice beach image. And uh everything is based on bootstrap, so it's all kind of based on a grid system. So you could do a grid with a column. And you can specify the column
Speaker 3: to, you know, up to the 12-column grid if you're familiar with Bootstrap. Each of our blocks also has these advanced settings that you can really go in and add custom CSS classes or you can actually change the individual HTML block uh HTML template that every block The block object uses to render, so that's another really useful thing. And we provide a ton of blocks out of the box, pretty much for all the basic website stuff. Of course, text, buttons, images, all that good stuff, tables, uh Google Map is one. Carousel is another thing. These are all mostly bootstrap components. Latest pages is kind of cool because it lets you actually point it to another page on the site.
Speaker 3: Maybe you made a blog page and it will show you all the child pages, different stuff like that. So I'm just going to start out with some text. Welcome to my site. Make that an H2. I'll add a button below it. And that's it. I'm gonna publish this page. So kind of right out the gate, I have now created a homepage that has this hero unit with an image and with some content which is totally unreadable. But um, you know, all these editing abilities are are built in. So
Speaker 3: I'm also going to create a blog page. Underneath my homepage, whoops, that's not what I wanted to do add child page and we provide a handful of page types out of the box as well for forms and other things like that. So I'll create this article landing page. I'll call it blog, and I don't really want any content on here because I want it to show a list of all the child pages. So I'll publish that. Now under my blog page, I will add a child page.
Speaker 3: Which defaults to the article, page type, and all this is, you know, once again uh standard Wagtail page models that you can totally customize. And uh I will upload another, I'll choose the same image for the sake of time. And I can set some uh metadata and I can once again with the standard uh stream field that we provide of all kinds of stuff. Uh I'll go ahead and publish this. And I'm also, so I can get to that, I'm going to fly back to my homepage and just add kind of a blog preview to that homepage. Just a list of the blog pages.
Speaker 3: So below my hero unit, I'll add a new section which can just be a grid, and I'll do a single column and inside that column I'm going to pick I want to show the latest pages that belong to my blog page And if I had any categories, I could also filter by that. Yes, let's show preview. And Let's I can actually change this to some different uh templates. So we'll do a article media format. Let's publish that. And now back on my homepage here, I have a list of blog posts. And if I click on my blog post, I go To my actual blog page.
Speaker 3: So all this stuff is built in out of the box, the search. I can search my different page types here. So it's really a great starting point when you need kind of that WordPress style ability to just go in and start adding content and clicking buttons to get things loaded in. And I've probably gone a little over five minutes, so uh I'll stop sharing now. But uh uh check it out. It's GitHub CodeRed CMS. Thanks.
Speaker 1: I think you've got a lot of questions to answer in Slack, Vic.
Speaker 3: Oh, okay. Whoops. So I don't want to take up too much more lightning talk time, so I'll I'll answer the questions in Slack or after the talks are done, I'll hang out on Zoom.
Speaker 1: Cool. Thanks so much, Vince.
Speaker 3: Mm-hmm.
Speaker 1: Next up we have uh Paul Smith 's to talk about testing a Wagtail site with Factory Boy. Hi
Speaker 4: there everybody. Can you hear me?
Speaker 1: Sounds great, Paul.
Speaker 4: Great. I'll just uh jump in right to it. My screen should be visible now Do does it show a site?
Speaker 1: Yep.
Speaker 2: Yeah, we've got it.
Speaker 4: Okay, great. Well, um this is uh just some context about the um The subject. It's about testing, and the testing is about this site. And this site actually is on the creation of the site itself. So what it does is it contains all the elements of its own creation in 32 at the moment, 32 video tutorials and written tutorials. And obviously when um so what it has for example, it has a user model, um user database about it. It has Let me drag this down a bit. Uh it has a contact form. Uh it has multiple languages. So you can have it in French or in Dutch And um there are several um sections, categories if you want. Let's go back to the homepage.
Speaker 4: And one of them is obviously testing. Um and what I wanted to talk about is let's change this back into English again. What I wanted to talk about is testing in Factory Boy. So when I developed this site, when it came to testing, I was in doubt how to set up the testing. And obviously one of the ways to do that is to use fixtures. I looked up at the Wagdell page itself. Uh so in g in GitHub, this is the test fixture that Wagdale one of the test fixtures that Wagdale itself is using. And you can see it's Quite big. It has 1100 lines and it's number of pages in it. And uh well, it takes quite some time to set up. And because I had some many-to-many relationships and foreign
Speaker 4: keys and some other things it I realized it would take me quite a lot of time to set up such a fixture file or maybe to uh make a backup of the side and use that as a fixture, but even that would take me a lot of time. So I look for another way to test it and I Looked at Factory Boy. Probably most of you will know that. What it will do is I'll just show you what it what it'll do because obviously one of the tutorials is on how this could be done. Um and what it what it does actually it creates factories for instances of models. So the best way to show that is to just show how how it's been done. You create a file, factory. py, and then you create a
Speaker 4: One of my models was a theme, so I created a theme factory, and this is basically all there is to it. You create a theme factory by just indicating what the model is, and in this case the model is theme. And in my case the theme had only one field, which is a name. And then what you can do is they you you can use a sequence to um to define those names because uh once you call uh once you instantiate a theme by calling the theme factory it's as simple as this then this field automatically gets filled by a lambda function. So the next time you call it, it will have a different name. It will have theme number zero, theme number one, theme theme number two, etc.
Speaker 4: And this is all pretty straightforward, but obviously if you want to test a Wagtail site, there's a number of things that you need to take into account. And one of the most important ones is to have a parent-child relationship Obviously, if you have pages, then you can have pages below that, below those pages. And that was something that I needed to establish in a factory because I didn't want to repeat that every time when I was instantiating the setup test data. So this is basically all the code that is to it. Can be done by overriding the create method. This is described in the factory boy documentation. There is this create method, and you can override it if you want to add s certain relationships to
Speaker 4: uh the model that you're to the factory that you're trying trying to create. And basically what I'm doing here is I'm Just looking for an argument argument parent. Let me see where it is. Yes, I'm looking for an argument argument parent in the keyword arguments. And if it's there, then what I just do is I create the page, just in the way that Factory Boy does that, and this is the way it's been done, and then I add that page to the parent I just found And if there's no parent specified, then I just assume that I'm dealing with a root page here. So then I attach it to the root page of the site I'm currently working with. And that's basically all to establish establish the parent -child relationship. Then if you want to create a homepage, this is an abstract model as you can see here.
Speaker 4: So if you want to create a homepage factory then this is the code that you do that you use because you subclass from page factory. Just use the model homepage again, just as we did with themes, and then you specify the title And with using a lambda function, you can specify a different title every time you would call this homepage factory. And you can do exactly the same thing with, for example, the theme pages I'm using, the theme index page I'm using, the article page I'm using, etc. And one of the things I mentioned was a many-to-manual relationship. And this is something that you can create in factory boy as well. Let me use the link for that. Here it is. So in factory boy
Speaker 4: you can um use the code to establish a many-to-many relationship. Here it is being done with a group and a user In my case, I used it with an article and a theme that an article belongs to. And obviously you can have different themes for diff for one article and you can have multiple articles per theme. So it is a many-to-many relationship And the way it's being done is very simple. This is all the code that you need actually to establish the many many relationship. And then if you get To the strong point of factory board, it's the setup of the test data because that is really very simple then if you've defined all these factories. And I'll just give you a um let me see if the GitHub, yes, here it is. This is the it's by
Speaker 4: by the way, it's public, so you can you can look it up if you want. I'm just going to the tests. And then here is the factories file. And the whole factories file is 119 lines. And it includes not only the pages, but it also includes a menu that I'm testing. And uh comments that I have on the page, I have a factory for that as well. And all of this is uh just over one hundred lines. And the setup of the test data is even simpler because It is you need to set up a site because that's something that Wagdale needs, as Kun van der Kamp this morning was explaining as well. Then I need a request factory for one of the tests. And then basically the setup of the pages is this. It's just one line per page. And you can specify in this case I didn't specify a parent because this is the root page actually.
Speaker 4: And in this case, I want to have this article index page To become a child of that homepage. And this is the way it's being done, as I just showed you with using the create method. And then I instantiate some themes, I have some theme pages, all of them below the theme index page and with the different themes as a foreign key. And then here there's the many-to-many relationship. Here I have multiple themes. Theme one and two belong to this article page and here in this case I only use theme one and obviously when I change my mind and I want to add another article page and more themes it's very easy to do as you can imagine And now to the actual testing, that's more or less the same as when using fixtures. So
Speaker 4: you can just use the elements that you've created here in the setup of the test data And in my case, let me see if I can have an example of that. Here is the this is the model for the article page I used So it is a translatable page which is um um done by Wagdeal Trans at the moment because I wasn't aware of the translatable mix-in, which is going to be in two point eleven I believe, but I didn't know that at the time. So there's a stream field here, there is a featured field, there's a per many-to-many relationship here, there are a couple of methods, there is a surf method, and this is something that I want to test obviously. So when doing that you can have different tests. Let me dive into one of them. For example, the theme page method. I want to test whether the
Speaker 4: theme pages actually are correctly mentioned. correctly um related to the uh article page. So I just make sure that when I make a theme page list using this theme pages method I just get the correct number of theme pages I would expect, and I also have the URL of those theme pages that I would expect Um and then here with the surf method, I use the request factory. Um I just make a request using the URL of the article page. I transform that into a response using this surf method and I just make sure that it is a correct response. That's basically all. And then after having done that, I obviously
Speaker 4: see how my code has been doing and if the tests have run correctly There's multiple tests here. This is one of the tests that's being done by Factory Boyd, but there's a couple of other ones. I used the comments. I used the language switch I designed and also the the menu and all of them are tested by Factory Boy. So you can have a look at it. I hope that this uh adds of value for you. Thank you very much.
Speaker 1: Thanks, Paul. That's awesome. I've actually been looking for pretty much exactly that solution for a site Ryan and I are working on. So that's a that's a wonderful help. Thank you. And uh last but not least, we have our final lightning talk of the day, which is going to be a tag team effort by Brian Witten and Kate Staten.
Speaker 5: Hi guys. So after the excitement that we saw earlier with my demo of Streams Build Splitter, I chased down Brian, who is the developer who worked on that for us. He no longer Is everything okay?
Speaker 1: Oh, you should be up in
Speaker 6: hello.
Speaker 5: Oh, hello. Um so Brian unfortunately no longer works with us, so I will do a quick demo just for anybody who didn't see it this morning of um the splitter in action in CMS and then I'll hand it over to Brian to talk to the technical side and answer any questions.
Speaker 1: Great.
Speaker 5: So here is my screen again. This is the article page. As many of you know it looks very familiar. I pre-populated our bacon ipsum this time around so you wouldn't have to see me copy and pasting. So if I scroll down here you see I have this long stream field of text and if I want to add an image I would have to create a new stream field. um have a paragraph stream field and copy and paste the text in order to then insert an image between. So we had some uh friction with our CMS users and that workflow. They people felt frustrated. It was taking a lot of time. Our team does a lot of embedding of content. They have a lot of Twitter links in their content. So it was just a lot of um work for them to have to do that. And they felt especially that
Speaker 5: as they were creating stories, they didn't always know where an embed or an image was going to go. So it was hard for them to anticipate. in advance what the need might be around where those breaks should be. So we put a lot of thought into this and came up with this idea of the stream field splitter , which as you guys saw this morning, lets you hover between paragraphs. And then
Speaker 6: Hey, one one thing I want to point out before you do that, we'll go into the code a little later, is if you watch where the mount like if you move your mouse over the paragraph. Um and the split, right, it like it changes where it is based on your position in the paragraph. Like it knows to go. to the next one or the previous one based where you are. So that's just something I saw on the code that I will point out. Okay, continue.
Speaker 5: That's a good point. It's also good to see that um So if I'm just like clicking within the text, it doesn't split. The only time you get the option to split is if you actually hover over the scissors and you see this cut line with the split here text And the reason we did that is because we didn't want people trying to click into the text to make edits or to type more content and then accidentally be splitting content. So you have to very specifically hover over the scissors. You click display here and then it separates your text into two different stream fields with the option to add whatever other fields in between that you might want. Before I give up my screen to pass to Brian, is there anything else that anyone wanted to see from this actual admin
Speaker 5: side? Okay, I'm gonna stop my share so Brian can pick it up.
Speaker 6: Cool. Uh to share my screen the button which desktop oh cool perfect okay Kate was very sneaky and Bring a what? It says what's going on. Oh. Ah, okay. Still getting used to Zoom. So Kate was very sneaky and she sent me the source code that I wrote that's currently closed source over at New York Public Radio. But I've Refamiliarize myself with this and I can kind of show everyone the interesting bits. Um make it a little bit bigger. Yes.
Speaker 6: Yeah, for sure. So this is mostly client side. There is obviously some um Python to hook up the various assets to get loaded on the admin screen, but It's mostly boilerplate and I think there was a lot of value in looking at it. This is really where everything happens. So uh Really, it's all encapsulated down here at the bottom under this ready function that runs when the page is loaded. Really a lot of this is driven by, if you remember, if you recall that little scissor icon
Speaker 6: , that is And obviously in the DOM, it's a node that's backed by an instance of a JavaScript class. that we hold in state and where it is in the DOM is driven basically by these two listeners here. This reposition method on this divider as we call it essentially detects where you are at the time and puts it in the actual DOM of the rich text editor. And the controlling the divider line that you show that you see is pretty much through CSS
Speaker 6: because Like I said, that is a that is a thing in the DOM. If you opened up the inspector and you looked at the DOM nodes of the rich text editor, you would see this thing, uh you know, the node associated with this thing. Um and really the splitting happens here with the split function You give it a divider and it does the magic. So let me go into that because that's really where most of the interesting stuff is. So this is the split function. It has a divider. Basically how this works is it interacts with uh DraftTail using the DraftTail API. And also using sort of the artifacts of the draft of how Draft
Speaker 6: Tail operates in the DOM. All of the text nodes in a rich text editor are considered blocks in a draft tail like runtime state. Each block has an ID essentially, and those IDs are attributes on the nodes in the text editor. So the basic flow for this is to b to say Given where that divider is in the DOM, gather up all the blocks literally before it in the DOM, find those IDs, find all the IDs after it, add
Speaker 6: in two new rich text editors And using the DraftAl API, copy over the blocks that we identified into their corresponding rich text editor blocks the new blocks and then of course delete the block the original block that's the one thing this is destructive right we're taking the text from the one you're currently working on, putting it into in into two new ones and getting rid of the old one. And there's a lot of stuff in here that Uh I'm not, I probably wouldn't do if I had more insight into things like I I'm not even sure if Wagtail has like a client-side API for controlling the state of a stream field.
Speaker 6: So I'm doing things like literally calling a click event on a DOM node in order to add in those new paragraph blocks. And then I have an async waiter set up to wait until there's the correct amount of DOM nodes and then it returns. It's a very interesting little system I set up here. Anyway, so this is so yeah so the boundaries um tells us uh basically where we are in the rich text editor. You get back an object with a previous next and basically says um The divider is between the previous and next keys. We can kind of
Speaker 6: see how that works. So we get the boundaries over here Right? Just look in the DOM, give me what's before it, give me what's after it. In this case, this dot node is that DOM element that is the divider that you're hovering over. And these functions read the attributes of those DOM nodes to give us the corresponding the IDs in the draft. js parlance. So we can just take a quick look at this function up here. You can see it basically looks at the data. There's a data attribute that's offset key. We do some fidgeting with it And we get back a value that we can feed into Draft.
Speaker 6: js later on. So I'm going to go back down to the split. And this is just giving us the rich text editor container in the DOM because it's like kind of an anchor point. And this is where we're doing that manual programmatic clicking of buttons. in the DOM. So this is saying given a container, add a paragraph. So this is where the everything before where the splitter is going to go. And then we pass it to it again, give us a second paragraph. And this is where the second half of things are going to go. And this is kind of a funny thing. I can show you this. Add paragraph after, wraps this thing, click for paragraph.
Speaker 6: Uh baby look for and we click it and uh I've set up this thing that's really saying uh it's uh set it's a timeout, it's an in that This function basically this is a shortcut for query selector all. This is basically saying um is the number of rich text editors different than from what I started and when it is resolved. And that's how I'm forcefully adding those two new paragraph blocks in the stream field. I think I will end with how text actually gets inserted, which was the result of lots of reading of the draft. js docs.
Speaker 6: But basically it's sort th they have if you dig deep enough and do enough um Munging of the data, you can essentially slice the blocks and then force feed them into a new uh draft instance and so basically every riched rich text block, every paragraph in a stream field is backed by a draft. js instance. And so you can access that instance through the DOM. There's an invisible input field that you can kind of get at and use that to access the in in memory of the draft. js editor
Speaker 6: And then you can use the API to feed in content. And that's basically all we're doing here. We have this new editor, which is the new paragraph that was added. We have our start net end keys, which is basically saying these are the this is everything from before the divider in one case, and then this is everything after the divider in the next case. Again, based on those data attributes that we pulled out of the DOM earlier. And this is all just a lot of hand-wavy stuff to get to here, to get the blocks that make up the content. of a draft. js instance. It has a slice method. And this is just like a regular array slice. You give it a start, you give it an end, and this is what we've got. And this is more draft draft. js boilerplate to basically say
Speaker 6: uh make a a state that's made up of my the blocks I'm interested in, I don't know, and then give that state to New editor again is the editor for inserting the content. And this is just how you do it. I don't, you know, there's a lot of Draft. js and the API docs are pretty inscrutable. Most of this work was just trying to understand how Draft. js works And you can see, you know, maybe you can tell me the difference between a content state and an editor state, but uh I certainly can't. And but you know, this is what works. Um Yeah, so that was a lot, but I'm happy to answer any questions people are interested.
Speaker 1: I've got a quick question. Can we PR this to Wagtail Core on Saturday?
Speaker 6: I don't know. As Kate said, I'm I'm out of there. This is uh I have no I had to ask her to send me this code, so it's really up to the fine folks at New York Public Radio. uh the public radio station of New York City to make that determination. But uh if I could be freewheeling for a second. I'm sure open sourcing our code is their code is in the interest of the public, and I think that's part of the mission. But again, I'll let Kate uh tell you yes or no. I don't know, Kate. What do you think?
Speaker 5: I'd love to do that. Um we've been really excited to open source the work we've done on Wagtail and to submit it back. I can't speak to whether or not it will happen this Saturday because I'd probably have to talk to the team that is no longer Brian just to see how people feel about it and who might be able to work on it. But I'll definitely let the team know that you all are interested. I just want to give Brian props again here because this is something I had been asking for for a really long time in our year working on Wagtail together and Brian gave notice and then basically on his last day of his two weeks notice, did this as a Hail Mary for me. So it's been great to have and the team loves it and I'm glad you all like it too.
Speaker 1: Very cool.
Speaker 6: Yeah, this is my um I was there for five years. This is like my parting gift, I guess.
Speaker 2: That's such a classy move to quit and then just like give them your best work on your last day. I love it.
Speaker 1: Miss me forever.
Speaker 6: Yeah. Honestly, I just did it for the credit Um this yeah, this was This is rewarding. This is one of those things that make working on a platform worth it because I got to see some editors and reporters using this in their workflow and You know, a big part of working at a republic radio station is to help Get news and information out to the public easier and faster. And this was, you know, this helped them be more efficient in doing that. So it was definitely a worthwhile endeavor.
Speaker 7: I've got a question. Um I had once an ID to make um a stream field block that just accepted uh Pasteboard content. Like if you would go into Word or Google Docs and you would copy your content into your pasteboard and go to a rectal stream field and have a blog there and paste it. Could you in a similar way create stream field blocks for each, like for a heading, for a text part, for an image that's already in your pasteboard?
Speaker 6: Possibly, yes. So a couple things come to mind. One is stripping any invisible control characters you'd get from copying out of a thing like Microsoft Word or Google Docs. That's like just like a general sanitation sanitization thing you'd want to do. than figuring out like how do you know based on whatever Google Docs report gives you Whether a selected block of text is an image caption, an image itself, you know, so you'd have to kind of figure out how to distinguish the types. And then, of course, like you can see I'm doing here, there's no real
Speaker 6: public API for controlling the stream field. I'm literally using a DOM query selector to find the new paragraph button and then I'm using the native DOM API to call the click method, which obviously trick Wag tail JavaScript listener to do whatever it needs to do to open the drawer, add the paragraph, update whatever state exists. So that would be another challenge to figure out a way to kind of like standardize the adding of extreme field blocks. This is also not super performant. I think Kate's video lag
Speaker 6: is probably hiding some of the jitter, but there is a little bit of a jitter as you know, recalling the click method and waiting for things to settle before moving on. And then You know, but these are all not insurmountable. These are like things that you'll want to be very organized about if you're pursuing that kind of thing. And then, of course, there's like actually putting text in a rich text block programmatically. And as you can see here, it's uh it's it's, you know, not exactly intuitive. Though I do think if we were dealing with just straight text, um WAGT uh draft JS
Speaker 6: does have some APIs around that. Uh but also want to remember things like links, hyperlinks, like formatting, bold, italic, headings, stuff like that. That's where getting that like parody is is very tough. because I didn't even get into there's a whole other world for draft. I think they call them entities basically. It's like metadata about text, which is how you can do like uh a Twitter handle in a piece of text or something like that. Um so you know Great. I think it's I think it's super useful. I think you
Speaker 6: get a lot of interest and use out of something like that from the Wagtail community. want you to go into it with eyes wide open.
Speaker 1: Awesome. Well thanks so much. That was a great talk and thanks to everybody who gave lightning talks today and all the speakers. I got a round of applause for everybody who uh spoke today. Thank you, thank you. We're uh we're wrapping up for the day now, but uh we'll be back tomorrow at 11 a. m. and we'll leave this channel open. So if anybody wants to socialize or ask questions, either on here in chat. Or in Slack in the Wagtail Space US 2020 channel, feel free.
Keep the regular Wagtail/CodeRed pages, add a React component where it’s useful, and serve its data from a single JSON endpoint. The example builds the meeting data from Wagtail models and returns it for the React app to render.
Discussed at 3:06The speaker’s solution is a custom template loader, exposed through a `MarkdownView`, that uses Python-Markdown to render the file and serve it as a page. The loader processes the template multiple times so images work correctly.
Discussed at 14:06Install the `coderedcms` package, run `coderedcms start` with a project name, then migrate the database, create a superuser, and start Django’s development server.
Discussed at 16:36It adds a large set of ready-to-use StreamField blocks and page types, along with options such as SEO fields, template selection, content walls, and Bootstrap-based layout tools.
Discussed at 20:32Define factories for your models and override page creation to attach each page to a specified parent—or to the site root by default. Factories can also set up many-to-many relationships, making test setup concise and easy to extend.
Discussed at 28:38The StreamField splitter adds a scissors control between paragraphs. Editors hover over it and choose where to split; the text becomes two separate StreamField blocks, leaving room to add an image or other content between them.
Discussed at 39:00Client-side JavaScript repositions a divider within the rich-text editor, identifies the Draft.js blocks on either side, creates two new rich-text blocks, copies the content into them, and removes the original block.
Discussed at 42:47Note: We understand that names change, people change, and bodies change. We respect each individual's journey and privacy. If you have any concerns about a video or need us to remove content, please don't hesitate to contact us. We will handle your request with care and promptly address any issues.
Published July 19, 2024
Published July 19, 2024
Published July 19, 2024
Published July 19, 2024
Published July 19, 2024
Published July 19, 2024