One place for hosting & domains

      Relationships

      How To Use Relationships to Select HTML Elements with CSS


      The author selected the Diversity in Tech Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      Selectors are what web browsers use to find and apply specific styles to an element via CSS. You can apply these styles broadly with type selectors that select many elements of the same kind. In addition to targeting specific attributes of an element like id or class, it is possible to select an element based on its relationship or proximity to another element. For example, you can select an HTML element that follows a preceding element or the first element in a series.

      In this tutorial, you will use several CSS-relationship-based approaches to select and style elements on an HTML page. You will create a page of content with different styling scenarios for each relationship selector. You will use the descendant combinator, child combinator, general sibling combinator, and adjacent sibling combinator, as well as the first-, last-, only-, and nth-child pseudo-class selectors. These selectors apply styles based on the relative conditions of surrounding elements, as opposed to the direct method of traditional selectors.

      Prerequisites

      Setting Up the Initial HTML and CSS

      To start, you will set up the HTML and CSS code that you will work on throughout the rest of the tutorial.

      Begin by opening index.html in your text editor. Then, add the following HTML to the file:

      index.html

      <!doctype html>
      <html>
        <head>
        </head>
        <body>
        </body>
      </html>
      

      Next, go to the <head> tag and add a <meta> tag to define the character set for the HTML file. Then set the title of the page, add a <meta> tag defining how mobile devices should render the page, and finally load the CSS file that you will make later with a <link> tag. These additions are highlighted in the following code block. You will encounter this highlighting method throughout the tutorial as code is added and changed:

      index.html

      <!doctype html>
      <html>
        <head>
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width, initial-scale=1">
          <title>Relationship Selectors</title>
          <link rel="stylesheet" href="https://www.digitalocean.com/community/tutorials/styles.css">
        </head>
        <body>
        </body>
      </html>
      

      After adding the <head> content, move to the <body> element, where you will add content for a page talking about CSS selectors. Add the highlighted section from this code block to your index.html file in your text editor:

      index.html

      <!doctype html>
      <html>
        <head>
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width, initial-scale=1">
          <title>Relationship Selectors</title>
          <link rel="stylesheet" href="https://www.digitalocean.com/community/tutorials/styles.css" />
        </head>
        <body>
          <h1>Relationship Selectors</h1>
          <main class="content-width">
          </main>
        </body>
      </html>
      

      Be sure to save these additions to index.html. The <h1> element displays the title of the page. The <main> element with a class attribute of content-width will contain the contents of the page, which you will add throughout the tutorial.

      Next, create a new file called styles.css in the same directory as your index.html file, then open the new file in your text editor. This file will load the styles that the browser will then apply to the content of index.html. Add the following CSS code to your styles.css file:

      styles.css

      body {
        font-family: system-ui, sans-serif;
        color: #333;
      }
      
      .content-width {
        width: 90%;
        margin: 2rem auto;
        max-width: 70ch; 
      }
      
      section{
        margin: 4rem 0;
      }
      
      h1 {
        text-align: center;
      }
      
      h2 {
        color: mediumblue;
      }
      

      In this code, the body type selector defines new defaults for the font-family and color values on the page. The .content-width class selector is set up to be 90% of the page’s width and to grow to a maximum width of 70 character widths of the current font-size. 70 characters is the ideal maximum length of a line of text. The margin on the .content-width property keeps space above and below the element and keeps the container horizontally centered. There will be several <section> elements on this page, so the section type selector applies a margin to give ample space between each grouping. Lastly, the h1 and h2 type selectors set the <h1> content to be centered and the <h2> content to have a color of mediumblue.

      In this section you, set up the starting points for your HTML and CSS files. In the next section, you will add some more content to index.html, then use the descendant combinator to apply styles with CSS.

      Descendant Combinators

      When working with relationship-based selectors, you will come across familial terminology, such as parent, child, ancestor, and in this case, descendant. The descendant combinator is a space-separated list of selectors that matches the HTML structure in order to apply styles to elements that have a particular ancestor. The last selector in the list is the one that receives the styles, and is the descendant of the previous selectors in the list.

      The descendant combinator is the quickest way to scope a style. Scoping is the method of providing additional details to increase the specificity of a selector. The descendant combinator aids scoping by prepending the selector with an ancestor selector. For example, the a type selector will get all the <a> tags on a page, but the header a descendant selector will select only the <a> tags inside of a <header> element.

      To begin using the descendant combinator, open index.html in your text editor. Create a <section> element with a class attribute of descendant. Inside the <section> element, add an <h2> element containing Descendant Combinator Selector, then add two <p> elements and fill them with content from Cupcake Ipsum. Wrap the second <p> element in a <blockquote>. The highlighted HTML in the following code block demonstrates how this will be set up:

      index.html

      ...
      <main class="content-width">
        <section class="descendant">
          <h2>Descendant Combinator Selector</h2>
          <p>Sweet roll pudding ice cream jelly beans caramels cookie caramels. Macaroon cheesecake cookie marzipan icing jujubes. Chocolate bar jelly-o wafer toffee cookie muffin soufflé lemon drops bonbon. Soufflé danish gingerbread sweet roll marzipan carrot cake.</p>
      
          <blockquote>
            <p>Bear claw pastry tootsie roll biscuit jujubes oat cake toffee wafer lemon drops. Croissant pie lemon drops cake chupa chups chocolate bar chupa chups marshmallow. Cake pudding icing tiramisu tiramisu pastry topping. Gingerbread shortbread lollipop chocolate bar icing.</p>
          </blockquote>
        </section>
      </main>
      ...
      

      Save your changes to index.html then open styles.css in your text editor. Add a .descendant class selector followed by a space, then a blockquote type selector. This will create a selector that only applies styles to a blockquote element that is inside an element with a class attribute containing descendant. The highlighted CSS in the following code block demonstrates this selector with styles for the blockquote element:

      styles.css

      ...
      h2 {
        color: mediumblue;
      }
      
      .descendant blockquote {
        margin: 2rem 0;
        padding: 1rem 1rem 1rem 2rem;
        border-left: 0.125rem indigo solid;
        background-color: lavender;
      }
      

      Save your changes to styles.css and open index.html in your browser. The properties used on this blockquote element provide additional spacing above and below with the margin property. Then, the padding property gives the content space inside the container. Lastly, the container is visually defined by a combination of the border-left property creating a solid indigo line and a lavender backdrop applied with the background-color property. The following screenshot shows how this code will render in the browser:

      Large bold blue headline followed by a smaller paragraph of black text. Below the paragraph is a light purple box with a darker purple border on the left side. Inside the box is another paragraph of black text.

      You can also apply descendant combinator selectors to a descendant element regardless of how deep in the HTML that element is relative to the ancestor selector. Similar to how the previous code set styles to the <blockquote> element inside of the .descendant class selector, you will next apply styles to all <p> elements, regardless of the element they are contained in.

      Return to your styles.css file in your text editor. Then, add a new descendant combinator selector for a p element selector with an ancestor of .descendant. Inside the selector block, set the line-height property to a value of 1.5. The highlighted CSS in the following code block shows how this will look:

      styles.css

      ...
      .descendant blockquote {
        margin: 2rem 0;
        padding: 1rem 1rem 1rem 2rem;
        border-left: 0.125rem indigo solid;
        background-color: lavender;
      }
      
      .descendant p {
        line-height: 1.5;
      }
      

      Save your changes to styles.css and open index.html in your browser. The lines of text for both <p> elements are now larger, despite one of those <p> elements being inside the <blockquote> element. The following image illustrates how this will render in the browser:

      Large bold blue headline followed by a paragraph of black text with a more legible line height. Below the paragraph is a light purple box with a darker purple border on the left side. Inside the box is another paragraph of black text with similar line height as the previous paragraph.

      Finally, return to your styles.css file in your text editor. You will create a new descendant selector to apply styles directly to the <p> element inside the <blockquote> element. Start by writing out .descendant blockquote p, which will tell the browser to find the <p> elements on the page that is somewhere inside a <blockquote> element, which in turn is inside an element with a class attribute of descendant. Then in the selector block, add a font-size property set to 1.25rem and a color property with #222 as the value. The highlighted CSS in the following code block demonstrates the necessary structure:

      styles.css

      ...
      .descendant p {
        line-height: 1.5;
      }
      
      .descendant blockquote p {
        font-size: 1.25rem;
        color: #222;
      }
      

      Save your changes to styles.css and open index.html in your browser. The font-size of the <p> element’s content inside the <blockquote> will now be much larger and have a slightly softer gray color, as shown in the following image:

      Large bold blue headline followed by a smaller paragraph of black text. Below the paragraph is a light purple box with a darker purple border on the left side. Inside the box is another paragraph of larger black text.

      In this section, you used the descendant combinator selector to scope styles to elements that are found inside of another element to apply styles. In the next section, you will use the child combinator selector to select only the first-level children of an element.

      Child Combinator Selectors

      When scoping styles, there are times that you don’t want all elements within an ancestor selector, just those immediately inside the element. For example, you might want to style all the top-level links in a navigation bar, and not any links that are contained in any sub navigation elements. For these moments when only the children of a given parent need to be styled, you can use the child combinator selector.

      Before working with this new selector, return to index.html in your text editor and add the highlighted HTML from the following code block inside the <main> element and after the previous <section> element:

      index.html

      ...
      <main class="content-width">
        ...
        <section class="child">
          <h2>Child Combinator Selector</h2>
      
          <p>Sweet roll carrot cake cake chocolate bar sugar plum. Cheesecake caramels jelly-o sugar plum icing muffin marzipan chocolate bar jujubes. Dessert cotton candy gummies chocolate cake sweet.</p>
      
          <div>
            <p>Liquorice sesame snaps chocolate bar soufflé oat cake candy canes fruitcake lollipop candy. Macaroon wafer cotton candy tootsie roll jelly halvah. Icing powder soufflé toffee dessert gummies bear claw donut cake.</p>
          </div>
        </section>
      </main>
      ...
      

      Save your changes to index.html then open styles.css in your text editor.

      First, you will extend the .descendant p selector with a line-height property to include the <p> elements found inside the <section> with the child class. You will accomplish by using a general combinator, which allows multiple selectors to share the same styles. A comma is needed between each selector to apply the styles. Add a comma after the .descendant p selector, then add a .child p descendant combinator, as demonstrated in the following highlighted code:

      styles.css

      ...
      .descendant p,
      .child p {
        line-height: 1.5;
      }
      ...
      

      Save your changes to styles.css and open index.html in your browser. The content in the new section will now have a larger space between each line of text. The following image depicts how this will appear in the browser:

      Large bold blue headline followed by two smaller sized black text paragraphs.

      Next, to select only the <p> element, which is the direct child of the <section class="child"> element, you will use the child combinator selector. This is constructed by writing the parent selector followed by the greater-than sign (>) and then the child selector.

      Return to styles.css in your text editor and go to the end of the file. Create a child combinator selector by setting the parent selector as .child and the child selector as p. Then inside the selector block set the color property to a named value of forestgreen. The highlighted CSS in the following code block shows how this is written:

      styles.css

      ...
      .descendant blockquote p {
        font-size: 1.25rem;
        color: #222;
      }
      
      .child > p {
        color: forestgreen;
      }
      

      Save your changes to styles.css and open index.html in your browser. Only the first <p> element has text set to the forestgreen color. The <div> element wrapping the second <p> element changes the relationship to the <section> element from a child to a grandchild. Because of this, that <p> element is invalid for the child combinator selector. The following image illustrates how this is rendered in the browser:

      Large bold blue headline followed by a smaller sized green text paragraph then a similar sized black text paragraph.

      In this section, you used the child combinator selector to select and apply styles to the immediate children of a parent element. In the next section, you will work with relationships of sibling elements to apply styles to elements when they have a common parent.

      Sibling Combinator Selectors

      The general sibling combinator provides a way to scope styling to elements that come after a specific element and have the same parent element, but which may not be next to each other. Often this will occur when the HTML is generally known but not predictable, such as dynamically generated content.

      First, you will need HTML siblings to use the selector. Return to index.html in your text editor. Then, add the highlighted HTML from the following code block before the closing </main> tag:

      index.html

      ...
      <main class="content-width">
        ...
        <section class="general-sibling">
          <h2>General Sibling Combinator Selector</h2>
      
          <p>Donut dessert jelly-o pie gingerbread jelly-o gummies biscuit gummies. Fruitcake jelly bonbon croissant carrot cake gummies. Candy canes apple pie liquorice gummi bears shortbread lemon drops jelly-o marzipan halvah. Jujubes chocolate bar tart bear claw sweet.</p>
      
          <div>
            <p>Carrot cake soufflé oat cake gummies marzipan sugar plum pastry jujubes. Tootsie roll pastry danish cake cake cake jelly sesame snaps. Donut pastry brownie brownie pie croissant</p>
          </div>
      
          <p>Gummies apple pie gingerbread cheesecake chupa chups cookie jelly beans. Tootsie roll dessert liquorice jujubes apple pie biscuit gummies biscuit jelly-o. Cake candy canes danish sugar plum biscuit lemon drops icing.</p>
      
          <p>Jelly beans candy candy cookie cotton candy. Liquorice gummies biscuit dragée sesame snaps oat cake tiramisu. Powder sweet dessert chupa chups ice cream sweet.</p>
        </section>
      </main>
      ...
      

      Save your changes to index.html, then open styles.css in your text editor.

      Next, you will extend the grouping selector with a line-height property to include the <p> elements found inside the <section> with the general-sibling class. Again, this is accomplished by adding a comma after the .child p selector and adding a .general-sibling p descendant combinator, as shown in the following highlighted CSS:

      styles.css

      ...
      .descendant p,
      .child p,
      .general-sibling p {
        line-height: 1.5;
      }
      ...
      

      Save your changes to styles.css and refresh index.html in your browser. The content in the new section will now have a larger space between each line of text. The following image depicts how this will appear in the browser:

      Large bold blue headline followed by four smaller-sized black text paragraphs.

      Next, you will write the styles for the sibling <p> elements that come after another <p> element of the same parent. This is done by writing a selector, followed by a tilde (~), followed by the selector of the sibling you wish to style.

      Return to styles.css in your text editor and add a new selector for .general-sibling p. This will find the <p> elements with a parent that has a class attribute with general-sibling. Then add the tilde symbol followed by a p element selector. Inside the selector block, set the color property to the named value of lightseagreen, as shown in the highlighted CSS of the following code block:

      styles.css

      ...
      .child > p {
        color: forestgreen;
      }
      
      .general-sibling p ~ p {
        color: lightseagreen;
      }
      

      Save your changes to styles.css and return to your browser to refresh index.html. The following image showcases how this code will render in the browser:

      Large bold blue headline followed by two smaller-sized black text paragraphs then two similar-sized teal text paragraphs.

      Only the last two of the four paragraphs will have the lightseagreen color. This is because the first paragraph is the initializing selector, the .general-sibling p part of the combinator, meaning the subsequent <p> elements will receive the styling. The second paragraph of text is inside a <p> element, but because it is nested inside a <div> element it is no longer a sibling of the initializing selector.

      Sibling selectors do not have to be of the same element type. To demonstrate this, return to the styles.css file in your text editor and change the first p in the selector to a div instead, as highlighted in the following code block:

      styles.css

      ...
      .general-sibling div ~ p {
        color: lightseagreen;
      }
      

      Save that small change to styles.css and refresh index.html in your browser. Visually nothing changed, but the way the styles are applied by the browser did change. The elements receiving the styles must be <p> elements that are sibling subsequents to the selector before the tilde. Since the order of the HTML is <p />, <div />, <p />, then <p />, the results of both general sibling selectors are the same. The first <p> element does not receive the styles because, though it is a sibling, it comes before the initializing .general-sibling div selector.

      In this section, you used the general sibling combinator to apply styles to any sibling elements of a certain kind. In the next section, you will use the adjacent sibling combinator to apply styles to the sibling immediately after an element.

      Adjacent Sibling Combinator Selectors

      If you only need to apply a style when two particular elements are next to each other in the HTML, the adjacent sibling combinator will help select the appropriate element. This can be useful when applying additional space between a heading text and a body text, or to add a divider between elements.

      To start working with the adjacent sibling combinator, first open index.html in your text editor. Then, inside the <main> element, add the highlighted HTML from the following code block:

      index.html

      ...
      <main class="content-width">
        ...
        <section class="adjacent-sibling">
          <h2>Adjacent Sibling Combinator Selector</h2>
      
          <p>Donut dessert jelly-o pie gingerbread jelly-o gummies biscuit gummies. Fruitcake jelly bonbon croissant carrot cake gummies. Candy canes apple pie liquorice gummi bears shortbread lemon drops jelly-o marzipan halvah. Jujubes chocolate bar tart bear claw sweet.</p>
      
          <div>
            <p>Carrot cake soufflé oat cake gummies marzipan sugar plum pastry jujubes. Tootsie roll pastry danish cake cake cake jelly sesame snaps. Donut pastry brownie brownie pie croissant</p>
          </div>
      
          <p>Gummies apple pie gingerbread cheesecake chupa chups cookie jelly beans. Tootsie roll dessert liquorice jujubes apple pie biscuit gummies biscuit jelly-o. Cake candy canes danish sugar plum biscuit lemon drops icing.</p>
      
          <p>Jelly beans candy candy cookie cotton candy. Liquorice gummies biscuit dragée sesame snaps oat cake tiramisu. Powder sweet dessert chupa chups ice cream sweet.</p>
        </section>
      </main>
      

      Save your changes to index.html, then open styles.css in your text editor. Extend the selector grouping of the line-height with .adjacent-sibling p, as highlighted in the following code block:

      styles.css

      ...
      .descendant p,
      .child p,
      .general-sibling p,
      .adjacent-sibling p {
        line-height: 1.5;
      }
      ...
      

      Next, go to the last line of the styles.css file. Here, you will write an adjacent sibling combinator selector to apply a top border and extra padding to a <p> element that is preceded by another <p> element. The highlighted CSS in the following code block displays how this is written:

      styles.css

      ...
      .general-sibling div ~ p {
        color: lightseagreen;
      }
      
      .adjacent-sibling p + p {
        border-top: 1px solid black;
        padding-top: 1em;
      }
      

      Since this style should only be applied to a <p> element inside the <section class="adjacent-sibling"> element, the first selector must use a descendant combinator. The second element in the selector only needs to use an element selector, since that is the adjacent element to the sibling.

      Note: It is important to remember when working with the sibling selectors that the last selector in the sequence is the element that is selected. Let’s say an adjacent sibling combinator was written as .adjacent-sibling p + .adjacent-sibling p. The intent of this code may be to select the <p> after a <p> inside an ancestor of .adjacent-sibling. But instead, this adjacent sibling selector will try to target the <p> inside an ancestor of .adjacent-sibling that comes immediately after a <p> sibling, which in turn is a descendant of another element with a class of adjacent-sibling.

      Save your changes to styles.css and refresh index.html in your browser. The border will be rendered above the last paragraph, since this is where a <p> is next to a <p>. The extra padding on top visually compensates for the margin so there is equal spacing above and below the line, as shown in the following image:

      Large bold blue headline followed by four smaller-sized black text paragraphs with a thin black rule line between the third and fourth paragraphs.

      This selector is not limited to like siblings; it can be a sibling selector of any kind. Return to styles.css and create a new adjacent sibling selector to add a red border between the <div> element and the adjacent <p> element, along with some extra top padding. The highlighted CSS in the following code block demonstrates how this will be set up:

      styles.css

      ...
      .adjacent-sibling p + p {
        border-top: 1px solid black;
        padding-top: 1em;
      }
      
      .adjacent-sibling div + p {
        border-top: 1px solid red;
        padding-top: 1em;
      }
      

      Save your changes to styles.css and open index.html in your browser. There is now a red line between the second and third paragraphs, since this is where a <div> element is next to a <p> element. The following image shows how this will be rendered in the browser:

      Large bold blue headline followed by four smaller-sized black text paragraphs with a thin red rule line after the second paragraph and a thin black rule line between the third and fourth paragraphs.

      In this section, you used the adjacent sibling combinator to apply styles to the element immediately following a sibling element. In the next section, you will use the first child pseudo-class to apply styles to the first element of a parent.

      first-child Pseudo-Class Selector

      When it comes to working with specific child elements, CSS provides pseudo-class selectors to refine the selection process. A pseudo-class selector is a state related to the condition of the selector. In this section, you will target elements that are the first child, meaning the first nested element, of a specified or unspecified parent element.

      To begin, open index.html in your text editor. Then, inside the <main> element, add the highlighted HTML from the following code block:

      index.html

      ...
      <main class="content-width">
        ...
        <section>
          <h2>First and Last Child Pseudo-Class Selector</h2>
          <ul>
            <li>Sugar plum gingerbread</li>
            <li>Sesame snaps sweet ice cream</li>
            <li>Jelly beans macaroon dessert</li>
            <li>Chocolate cheesecake</li>
            <li>Sweet roll pastry carrot cake</li>
            <li>Sugar plum tart cake</li>
            <li>Pudding soufflé</li>
            <li>Marshmallow oat cake</li>
          </ul>
        </section>
      </main>
      

      This HTML creates an unordered list, as defined by the <ul> element, with eight list items (<li>) nested inside. This will create a bulleted list of content by default in the browser.

      Save these changes to index.html then open styles.css in your text editor.

      In your CSS file, add a ul element selector. This section will not require any class-related scoping. Then inside the selector block add the property list-style with a value of none, which will remove the bulleted list styling. Set the margin property to 1rem 0 and the padding property to 0, which will override the browser default styles. The highlighted CSS in the following code block demonstrates how to set this up:

      styles.css

      ...
      .adjacent-sibling div + p {
        border-top: 1px solid red;
        padding-top: 1em;
      }
      
      ul {
        list-style: none;
        margin: 1rem 0;
        padding: 0;
      }
      

      Save your changes to styles.css and open index.html in your browser. The unordered list will no longer have bullets and will be aligned with the heading text, as shown in the following image:

      Large bold blue headline followed by an unordered list of eight items.

      Next, to provide some default styling to the list items, create a li element selector. Apply a padding property in the selector block with a value of 0.5rem 0.75rem. Then, add a light green background-color property with hsl(120, 50%, 95%) as the value. Lastly, add a border property set to 1px and solid with a slightly darker green color using hsl(120, 50%, 80%), as highlighted in the following code block:

      styles.css

      ...
      ul {
        list-style: none;
        margin: 1rem 0;
        padding: 0;
      }
      
      ul li {
        padding: 0.5rem 0.75rem;
        background-color: hsl(120, 50%, 95%);
        border: 1px solid hsl(120, 50%, 80%);
      }
      

      Save these changes to styles.css and open index.html in your browser. Each list item now has a light green background color with a darker green border, with text inset by the padding property. The following image shows how this will render in the browser:

      Large bold blue headline followed by an unordered list of eight items each in a light green box with a green border.

      The top and bottom borders of the list items double up the thickness of the border between items, making the border thickness inconsistent. To address this situation, return to styles.css in your text editor. Then, add a new adjacent sibling combinator to select an <li> element that comes after an <li> element inside a <ul> element, as depicted in the following code block:

      styles.css

      ...
      ul li {
        padding: 0.5rem 0.75rem;
        background-color: hsl(120, 50%, 95%);
        border: 1px solid hsl(120, 50%, 80%);
      }
      
      ul li + li {
        border-top: none;
      }
      

      Save that addition to styles.css and refresh index.html in your browser. The extra thick line between list items has been addressed by removing the border from the top of any list item that comes after another list item. The following image showcases how this will appear in the browser:

      Large bold blue headline followed by an unordered list of eight items each in a light green box with a green border.

      Now, return to styles.css to apply rounded corners to the top of the first list item. Since this needs to apply to only the first list item of an unordered list, you can use the child combinator selector. This will ensure that only the first direct child of the unordered list is selected. After the li element selector, append :first-child immediately without a space. For the rounded corner style on the top use the border-radius property with the value set to 0.75rem 0.75rem 0 0, which will add the rounded corners to the top left and top right corners. The highlighted CSS in the following code block shows how this is written:

      styles.css

      ...
      ul li + li {
        border-top: none;
      }
      
      ul > li:first-child {
        border-radius: 0.75rem 0.75rem 0 0;
      }
      

      Save this change to styles.css and return to the browser to refresh index.html. The first item in the unordered list now has rounded corners on the top side of the element. The following image illustrates how this is rendered in the browser:

      Large bold blue headline followed by an unordered list of eight items each in a light green box with a green border. The first item in the list has rounded corners.

      In this section, you created styles for a list and applied rounded corners to the first child <li> element of the <ul> parent element. In the next section, you will style the <li> at the end of the list with the last-child pseudo-class selector.

      last-child Pseudo-Class Selector

      In the same way that the first-child pseudo-class selector captures the first element of a parent, the last-child pseudo-class selector captures the last element of a parent element. In the previous section, you added rounded corners to the top of the first list item. In this section, you will add rounded corners to the bottom of the last item.

      To begin, return to styles.css in your text editor. After the selector block for the :first-child, add a similarly formatted child combinator with a :last-child on the li element selector. In the selector block, add a border-radius property and then for the value use 0 0 0.75rem 0.75rem. This value will set the top values to 0 and the bottom right and bottom left curve values to 0.75rem. The highlighted CSS in the following code block show how this is composed:

      styles.css

      ...
      ul > li:first-child {
        border-radius: 0.75rem 0.75rem 0 0;
      }
      
      ul > li:last-child {
        border-radius: 0 0 0.75rem 0.75rem;
      }
      

      Save your changes to styles.css and open index.html in your browser. The whole unordered list now looks like a box with equal rounded corners. The following images demonstrates how this is rendered in the browser:

      Large bold blue headline followed by an unordered list of eight, items each in a light green box with a green border. The first and last items in the list have rounded corners.

      In this section, you used the last-child pseudo-class to apply styles to the last <li> element in an unordered list. In the next, section you will write styles for when there is only one child element.

      only-child Pseudo-Class Selector

      When applying styles to contents that can change or update, there may be instances when there is only one child element. In those instances, the element is both the first and the last child, which can cause the pseudo-classes to overwrite each other’s styles. For this scenario there is another pseudo-class selector specifically for the only child scenario.

      To start, open index.html in your text editor and add the highlighted HTML from the following code block inside the <main> element and after the previous <section> element:

      index.html

      ...
      <main class="content-width">
        ...
        <section>
          <h2>Only Child Pseudo-Class Selector</h2>  
          <ul>
            <li>Sweet roll pastry carrot cake</li>
          </ul>
        </section>
      </main>
      

      Save the changes to index.thml then return to your browser and refresh the page. The browser is applying the :first-child rounded corners on the top, but then the :last-child rounded corner property overwrites the first. This results in the single list item having sharp corners on top and rounded corners on the bottom. The following image shows the browser rendering of the code:

      Large bold blue headline followed by an unordered list of one item with a light green background, green border, rounded corners on the bottom of the shape.

      Return to your text editor and open styles.css. To address this scenario, you will use the :only-child pseudo-class selector on the li portion of a child combinator. Then, for the border-radius property, set the value to a single 0.75rem, which will apply the curve to all four corners. The highlighted CSS in the following code block shows how this is written:

      styles.css

      ...
      ul > li:last-child {
        border-radius: 0 0  0.75rem 0.75rem;
      }
      
      ul > li:only-child {
        border-radius: 0.75rem;
      }
      

      Save these additions to styles.css, return to your browser, and refresh index.html. The single list item now has rounded corners on all sides, as shown in the following image:

      Large bold purple headline followed by an unordered list of one item with a light green background, green border, rounded corners on all four corners of the shape.

      In this section, you used the only-child pseudo-class selector to apply styles to a single <li> element in an unordered list. In the next section, you will use a pseudo-class selector to apply styles to any specific child element based on its numerical count within the parent.

      nth-child Pseudo-Class Selector

      The nth-child pseudo-class selector allows you to set patterns for the selection of child elements. Other selectors use characteristics of an element, such as an attribute value, to find matching HTML elements. The nth-child pseudo-class selector targets HTML elements by a given numerical position within their parent. The term nth is a mathematical phrase referring to an unidentified number in a series. This particular selector enables the selection of even and odd numbered children and specific numbered elements in the sequence.

      To set up the HTML for this selector, open index.html in your text editor. Then, add the highlighted portion from the following code block after the last </section> tag:

      index.html

      ...
      <main class="content-width">
        ...
        <section>
          <h2>Nth Child Pseudo-Class Selector</h2>
          <ol>
            <li>Caramel marshmallows</li>
            <li>Gummi bears</li>
            <li>pudding donut</li>
            <li>Chocolate bar</li>
            <li>Lemon drops</li>
            <li>Lollipop</li>
            <li>Danish soufflé</li>
          </ol>
        </section>
      </main>
      

      This code sets up an ordered list, as defined by the <ol> element. Unlike the <ul>, which marks each list item with a bullet point, the <ol> marks each item with a number in sequence.

      Be sure to save those changes to index.html. Then return to styles.css in your text editor. You will set up some adjusted stlyes for the ordered list and the list items. Create an ol element selector with a padding property set to 0. Next, add a list-style-position property with a value of inside; this will move the generated number to be inside the <li> element’s box model. Lastly, create an ol li descendant combinator with a padding property set to 0.25rem. Reference the highlighted CSS of the following code block for how this is to be written:

      styles.css

      ...
      ul > li:only-child {
        border-radius: 0.75rem;
      }
      
      ol {
        padding: 0;
        list-style-position: inside;
      }
      
      ol li {
        padding: 0.25rem;
      }
      

      Save your changes to styles.css, then open a web browser and load your index.html file. The new section will be rendered with a list of seven items, each beginning with their numerical value in the sequence, as illustrated in the following image:

      Large bold blue headline followed by an ordered list of seven items in black text.

      To begin working with the nth-child pseudo-class, return to styles.css in your text editor. The selector is set up like the previous pseudo-classes, but with a parenthesis at the end. This parenthesis contains the word value or numeric value to select the elements. To select all the even numbered list items, create a descendant combinator for ol and li. Then, add :nth-child() and inside the parenthesis add the word even. In the selector block, set the background-color property to aliceblue, as highlighted in the following code block:

      styles.css

      ...
      ol li {
        padding: 0.25rem;
      }
      
      ol li:nth-child(even) {
        background-color: aliceblue;
      }
      

      Save your changes to styles.css and open index.html in your browser. The second, fourth, and sixth items in the list now have a light blue background. This is how to create a style known as zebra stripes, which helps legibility with long lists of information. The following image demonstrates how this is rendered in the browser:

      Large bold blue headline followed by an ordered list of seven items in black text, alternating background betwen white and light blue.

      Next, return to styles.css in the text editor and do the same thing for the odd-numbered list items. Create another descendant combinator with the :nth-child() pseudo-class selector. This time, inside the parenthesis add the word value odd. Then in the selector block set the background-color property to lavenderblush, a light pink color. The following highlighted code shows how to set this up:

      styles.css

      ...
      ol li:nth-child(even) {
        background-color: aliceblue;
      }
      
      ol li:nth-child(odd) {
        background-color: lavenderblush;
      }
      

      Save that change to styles.css and reload index.html in the browser. The list now alternates between lavenderblush and aliceblue, with the new color on the first, third, fifth, and seventh list items. The following image shows how this will appear:

      Large bold blue headline followed by an ordered list of seven items in black text, alternating background betwen pink and light blue.

      Lastly, a numeric value can be inserted instead of a word value. Return to styles.css and add another ol li:nth-child() selector, this time with a 4 inside the parentheses. This will select the fourth item in the list. To make that item stand out, add a color property set to white and a background-color property set to indigo, as highlighted in the following code block:

      styles.css

      ...
      ol li:nth-child(odd) {
        background-color: lavenderblush;
      }
      
      ol li:nth-child(4) {
        color: white;
        background-color: indigo;
      }
      

      Save your changes to styles.css and open index.html in your browser. The odd and even items retain their alternating zebra stripe styling, but the fourth item now has a purple-colored background with white text. The following image shows how this is rendered in the browser:

      Large bold blue headline followed by an ordered list of seven items in black text, alternating background between pink and light blue with the fourth item having a purple background with white text.

      Note: The :nth-child() pseudo-class is able to also use algebric equations, such as 3n to select every third item in a list. CSS Tricks has a nth-child testing tool to test nth-child values to help understand the complex versatility of this pseudo-class selector.

      In this last section, you used the :nth-child() pseudo-class selector to apply styles to even-numbered elements, odd-numbered elements, and specifically the fourth element in the list.

      Conclusion

      In this tutorial, you have used many new selectors based on the concept of relationships. You used the descendant combinator and child combinator selectors to select elements inside other elements. Then, you used the general sibling combinator and adjacent sibling combinator to select elements that have a shared parent and apply styles based on proximity. Lastly, you used a series of pseudo-class selectors to style the first, last, and only child elements, along with every nth element in between. These selectors will help you hone and scope the styles of your projects.

      If you would like to read more CSS tutorials, try out the other tutorials in the How To Style HTML with CSS series.



      Source link

      How To Use Many-to-Many Database Relationships with Flask and SQLite


      The author selected the COVID-19 Relief Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      Flask is a framework for building web applications using the Python language, and SQLite is a database engine that you can use with Python to store application data. In this tutorial, you’ll modify an application built using Flask and SQLite by adding a many-to-many relationship to it.

      Although you can follow this tutorial independently, it is also a continuation of the How To Modify Items in a One-to-Many Database Relationships with Flask and SQLite tutorial in which we managed a multi-table database with a one-to-many relationship using a to-do application example. The application allows users to add new to-do items, categorize items under different lists, and modify items.

      A many-to-many database relationship is a relationship between two tables where a record in each table can reference several records in the other table. For example, in a blog, a table for posts can have a many-to-many relationship with a table for storing authors. Each post can reference many authors, and each author can reference many posts. Each post can have many authors, and each author can write many posts. Therefore, there is a many-to-many relationship between posts and authors. For another example, in a social media application, each post may have many hashtags, and each hashtag may have many posts.

      By the end of the tutorial, your application will have a new feature for assigning to-do items to different users. We will refer to the users that get assigned to-dos with the word assignees. For example, you can have a household to-do item for Cleaning the kitchen, which you can assign to both Sammy and Jo—each to-do can have many assignees (that is, Sammy and Jo). Also each user can have many to-dos assigned to them (that is, Sammy can be assigned multiple to-do items), this is a many-to-many relationship between to-do items and assignees.

      At the end of this tutorial, the application will include an Assigned to tag with the names of the assignees listed.

      Todo Application

      Prerequisites

      Before you start following this guide, you will need:

      Step 1 — Setting Up the Web Application

      In this step, you will set up the to-do application ready for modification. You will also review the database schema to understand the structure of the database. If you followed the tutorial in the prerequisites section and still have the code and the virtual environment on your local machine, you can skip this step.

      To demonstrate adding a many-to-many relationship to a Flask web application, you will use the previous tutorial’s application code, which is a to-do management web application built using Flask, SQLite, and the Bootstrap framework. With this application users can create new to-dos, modify and delete existing to-dos, and mark to-dos as complete.

      Clone the repository and rename it from flask-todo-2 to flask_todo with the following command:

      • git clone https://github.com/do-community/flask-todo-2 flask_todo

      Navigate to flask_todo:

      Then create a new virtual environment:

      Activate the environment:

      Install Flask:

      Then, initialize the database using the init_db.py program:

      Next, set the following environment variables:

      • export FLASK_APP=app
      • export FLASK_ENV=development

      FLASK_APP indicates the application you are currently developing, which is app.py in this case. FLASK_ENV specifies the mode—set it to development for development mode; this will allow you to debug the application. (Remember not to use this mode in a production environment.)

      Then run the development server:

      If you go to your browser, you’ll have the application running at the following URL: http://127.0.0.1:5000/.

      To stop the development server, use CTRL + C.

      Next, you will go through the database schema to understand the current relationships between tables. If you are familiar with the contents of the schema.sql file, you can skip to the next step.

      Open the schema.sql file:

      The file contents are as follows:

      flask_todo/schema.sql

      DROP TABLE IF EXISTS lists;
      DROP TABLE IF EXISTS items;
      
      CREATE TABLE lists (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
          title TEXT NOT NULL
      );
      
      CREATE TABLE items (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          list_id INTEGER NOT NULL,
          created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
          content TEXT NOT NULL,
          done INTEGER NOT NULL DEFAULT 0,
          FOREIGN KEY (list_id) REFERENCES lists (id)
      );
      

      In the schema.sql file, you have two tables: lists for storing lists (such as Home or Study), and items for storing to-do items (such as Do the dishes or Learn Flask).

      The lists table has the following columns:

      • id: The ID of the list.
      • created: The list’s creation date.
      • title: The list’s title.

      The items table has the following columns:

      • id: The ID of the item.
      • list_id: The ID of the list the item belongs to.
      • created: The item’s creation date.
      • content: The item’s content.
      • done: The item’s state, the value 0 indicates the item has not been done yet, while 1 indicates item completion.

      In the items table you have a foreign key constraint, in which the list_id column references the id column of the lists parent table. This is a one-to-many relationship between items and lists, indicating that a list can have multiple items, and items belong to a single list:

      FOREIGN KEY (list_id) REFERENCES lists (id)
      

      In the next step, you will use a many-to-many relationship to create a link between two tables.

      Step 2 — Adding an Assignees Table

      In this step, you will review how to implement a many-to-many relationship and joins table. Then you’ll add a new table for storing assignees.

      A many-to-many relationship links two tables where each item in a table has many related items in the other table.

      Let’s say you have a simple table for to-do items as follows:

      Items
      +----+-------------------+
      | id | content           |
      +----+-------------------+
      | 1  | Buy eggs          |
      | 2  | Fix lighting      |
      | 3  | Paint the bedroom |
      +----+-------------------+
      

      And a table for assignees like so:

      assignees
      +----+------+
      | id | name |
      +----+------+
      | 1  | Sammy|
      | 2  | Jo   |
      +----+------+
      

      Let’s say you want to assign the to-do Fix lighting to both Sammy and Jo, you could do this by adding a new row in the items table like so:

      items
      +----+-------------------+-----------+
      | id | content           | assignees |
      +----+-------------------+-----------+
      | 1  | Buy eggs          |           |
      | 2  | Fix lighting      | 1, 2      |
      | 3  | Paint the bedroom |           |
      +----+-------------------+-----------+
      

      This is the wrong approach because each column should only have one value; if you have multiple values, basic operations such as adding and updating data become cumbersome and slow. Instead, there should be a third table that references primary keys of related tables—this table is often called a join table, and it stores IDs of each item from each table.

      Here is an example of a join table that links between items and assignees:

      item_assignees
      +----+---------+-------------+
      | id | item_id | assignee_id |
      +----+---------+-------------+
      | 1  | 2       | 1           |
      | 2  | 2       | 2           |
      +----+---------+-------------+
      

      In the first row, the item with the ID 2 (that is, Fix lighting) relates to the assignee with the ID 1 (Sammy). In the second row, the same item also relates to the assignee with the ID 2 (Jo). This means that the to-do item is assigned to both Sammy and Jo. Similarly, you can assign each assignee to multiple items.

      Now, you will modify the to-do application’s database to add a table for storing assignees.

      First, open schema.sql to add a new table named assignees:

      Add a line to delete the assignees table if it already exists. This is to avoid potential future issues when reinitiating the database, such as an already existing assignees table with different columns, which might break the code unexpectedly if it does not follow the same schema. You also add the SQL code for the table:

      flask_todo/schema.sql

      DROP TABLE IF EXISTS assignees;
      DROP TABLE IF EXISTS lists;
      DROP TABLE IF EXISTS items;
      
      CREATE TABLE lists (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
          title TEXT NOT NULL
      );
      
      CREATE TABLE items (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          list_id INTEGER NOT NULL,
          created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
          content TEXT NOT NULL,
          done INTEGER NOT NULL DEFAULT 0,
          FOREIGN KEY (list_id) REFERENCES lists (id)
      );
      
      CREATE TABLE assignees (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          name TEXT NOT NULL
      );
      

      Save and close the file.

      This new assignees table has the following columns:

      • id: The ID of the assignee.
      • name: The name of the assignee.

      Edit the init_db.py program to add a few assignees to the database. You use this program to initialize the database:

      Modify the file to look as follows:

      flask_todo/init_db.py

      import sqlite3
      
      connection = sqlite3.connect('database.db')
      
      with open('schema.sql') as f:
          connection.executescript(f.read())
      
      cur = connection.cursor()
      
      cur.execute("INSERT INTO lists (title) VALUES (?)", ('Work',))
      cur.execute("INSERT INTO lists (title) VALUES (?)", ('Home',))
      cur.execute("INSERT INTO lists (title) VALUES (?)", ('Study',))
      
      cur.execute("INSERT INTO items (list_id, content) VALUES (?, ?)",
                  (1, 'Morning meeting')
                  )
      
      cur.execute("INSERT INTO items (list_id, content) VALUES (?, ?)",
                  (2, 'Buy fruit')
                  )
      
      cur.execute("INSERT INTO items (list_id, content) VALUES (?, ?)",
                  (2, 'Cook dinner')
                  )
      
      cur.execute("INSERT INTO items (list_id, content) VALUES (?, ?)",
                  (3, 'Learn Flask')
                  )
      
      cur.execute("INSERT INTO items (list_id, content) VALUES (?, ?)",
                  (3, 'Learn SQLite')
                  )
      
      cur.execute("INSERT INTO assignees (name) VALUES (?)", ('Sammy',))
      cur.execute("INSERT INTO assignees (name) VALUES (?)", ('Jo',))
      cur.execute("INSERT INTO assignees (name) VALUES (?)", ('Charlie',))
      cur.execute("INSERT INTO assignees (name) VALUES (?)", ('Ashley',))
      
      connection.commit()
      connection.close()
      

      Save and close the file.

      In the highlighted lines, you use the cursor object to execute an INSERT SQL statement to insert four names into the assignees table. You use the ? placeholder in the execute() method and pass a tuple containing the name of the assignee to safely insert data into the database. Then you commit the transaction with connection.commit() and close the connection using connection.close().

      This will add four assignees to the database, with the names Sammy, Jo, Charlie, and Ashley.

      Run the init_db.py program to reinitialize the database:

      You now have a table for storing assignees in the database. Next you will add a join table to create a many-to-many relationship between items and assignees.

      Step 3 — Adding a Many-to-Many Join Table

      In this step, you will use a join table to link to-do items with assignees. First you’ll edit your database schema file to add the new join table, edit the database initialization program to add a few assignments, then use a demonstration program to display the assignees of each to-do.

      Open schema.sql to add a new table:

      Because the table joins items and assignees, you will call it item_assignees. Add a line to delete the table if it already exists, then add the SQL code for the table itself:

      flask_todo/schema.sql

      DROP TABLE IF EXISTS assignees;
      DROP TABLE IF EXISTS lists;
      DROP TABLE IF EXISTS items;
      DROP TABLE IF EXISTS item_assignees;
      
      
      CREATE TABLE lists (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
          title TEXT NOT NULL
      );
      
      CREATE TABLE items (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          list_id INTEGER NOT NULL,
          created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
          content TEXT NOT NULL,
          done INTEGER NOT NULL DEFAULT 0,
          FOREIGN KEY (list_id) REFERENCES lists (id)
      );
      
      CREATE TABLE assignees (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          name TEXT NOT NULL
      );
      
      CREATE TABLE item_assignees (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          item_id INTEGER,
          assignee_id INTEGER,
          FOREIGN KEY(item_id) REFERENCES items(id),
          FOREIGN KEY(assignee_id) REFERENCES assignees(id)
      );
      

      Save and close the file.

      This new item_assignees table has the following columns:

      • id: The ID of the entry that establishes a relationship between to-dos and assignees; each row represents a relationship.
      • item_id: The ID of the to-do item that will be assigned to the assignee with the corresponding assignee_id.
      • assignee_id: The ID of the assignee who will get assigned the item with the corresponding item_id.

      The item_assignees table also has two foreign key constraints: one that links the item_id column with the id column of the items table, and another one linking between the assignee_id column with the id column of the assignees table.

      Open init_db.py to add a few assignments:

      Modify the file to look as follows:

      flask_todo/init_db.py

      import sqlite3
      
      connection = sqlite3.connect('database.db')
      
      
      with open('schema.sql') as f:
          connection.executescript(f.read())
      
      cur = connection.cursor()
      
      cur.execute("INSERT INTO lists (title) VALUES (?)", ('Work',))
      cur.execute("INSERT INTO lists (title) VALUES (?)", ('Home',))
      cur.execute("INSERT INTO lists (title) VALUES (?)", ('Study',))
      
      cur.execute("INSERT INTO items (list_id, content) VALUES (?, ?)",
                  (1, 'Morning meeting')
                  )
      
      cur.execute("INSERT INTO items (list_id, content) VALUES (?, ?)",
                  (2, 'Buy fruit')
                  )
      
      cur.execute("INSERT INTO items (list_id, content) VALUES (?, ?)",
                  (2, 'Cook dinner')
                  )
      
      cur.execute("INSERT INTO items (list_id, content) VALUES (?, ?)",
                  (3, 'Learn Flask')
                  )
      
      cur.execute("INSERT INTO items (list_id, content) VALUES (?, ?)",
                  (3, 'Learn SQLite')
                  )
      
      cur.execute("INSERT INTO assignees (name) VALUES (?)", ('Sammy',))
      cur.execute("INSERT INTO assignees (name) VALUES (?)", ('Jo',))
      cur.execute("INSERT INTO assignees (name) VALUES (?)", ('Charlie',))
      cur.execute("INSERT INTO assignees (name) VALUES (?)", ('Ashley',))
      
      # Assign "Morning meeting" to "Sammy"
      cur.execute("INSERT INTO item_assignees (item_id, assignee_id) VALUES (?, ?)",
                  (1, 1))
      
      # Assign "Morning meeting" to "Jo"
      cur.execute("INSERT INTO item_assignees (item_id, assignee_id) VALUES (?, ?)",
                  (1, 2))
      
      # Assign "Morning meeting" to "Ashley"
      cur.execute("INSERT INTO item_assignees (item_id, assignee_id) VALUES (?, ?)",
                  (1, 4))
      
      # Assign "Buy fruit" to "Sammy"
      cur.execute("INSERT INTO item_assignees (item_id, assignee_id) VALUES (?, ?)",
                  (2, 1))
      
      connection.commit()
      connection.close()
      

      In the highlighted code, you assign to-do items to assignees by inserting into the item_assignees join table. You insert the item_id of the to-do item you want to assign to the assignee with the ID corresponding to the assignee_id value. In the first highlighted line, you assign the to-do item Morning meeting, which has an ID of 1, to the assignee Sammy, who has an ID of 1. The rest of the lines follow the same pattern. Once again, you use the ? placeholders to safely pass the values you want to insert in a tuple to the cur.execute() method.

      Save and close the file.

      Run the init_db.py program to reinitialize the database:

      Run the list_example.py program that displays the to-do items you have on the database:

      Here is the output:

      Output

      Home Buy fruit | id: 2 | done: 0 Cook dinner | id: 3 | done: 0 Study Learn Flask | id: 4 | done: 0 Learn SQLite | id: 5 | done: 0 Work Morning meeting | id: 1 | done: 0

      This displays the to-do items under the lists they belong to. You have each item’s content, its ID, and whether it’s completed or not (0 means the item is not completed yet, and 1 means it’s completed). You now need to display the assignees of each to-do.

      Open list_example.py to modify it to display item assignees:

      Modify the file to look as follows:

      flask_todo/list_example.py

      from itertools import groupby
      from app import get_db_connection
      
      conn = get_db_connection()
      todos = conn.execute('SELECT i.id, i.done, i.content, l.title 
                            FROM items i JOIN lists l 
                            ON i.list_id = l.id ORDER BY l.title;').fetchall()
      
      lists = {}
      
      for k, g in groupby(todos, key=lambda t: t['title']):
          # Create an empty list for items
          items = []
          # Go through each to-do item row in the groupby() grouper object
          for item in g:
              # Get the assignees of the current to-do item
              assignees = conn.execute('SELECT a.id, a.name FROM assignees a 
                                        JOIN item_assignees i_a 
                                        ON a.id = i_a.assignee_id 
                                        WHERE i_a.item_id = ?',
                                        (item['id'],)).fetchall()
              # Convert the item row into a dictionary to add assignees
              item = dict(item)
              item['assignees'] = assignees
      
              items.append(item)
      
          # Build the list of dictionaries
          # the list's name (ex: Home/Study/Work) as the key
      
          # and a list of dictionaries of to-do items
          # belonging to that list as the value
          lists[k] = list(items)
      
      
      for list_, items in lists.items():
          print(list_)
          for item in items:
              assignee_names=", ".join(a['name'] for a in item['assignees'])
      
              print('    ', item['content'], '| id:',
                    item['id'], '| done:', item['done'],
                    '| assignees:', assignee_names)
      
      

      Save and close the file.

      You use the groupby() function to group to-do items by the title of the list they belong to. (See Step 2 of How To Use One-to-Many Database Relationships with Flask and SQLite for more information.) While going through the grouping process, you create an empty list called items, which will hold all of the to-do item data, such as the item’s ID, content, and assignees. Next, in the for item in g loop, you go through each to-do item, get the assignees of the item, and save it in the assignees variable.

      The assignees variable holds the result of a SELECT SQL query. This query gets the assignee’s id (a.id) and the assignee’s name (a.name) from the assignees table (which is aliased to a to shorten the query). The query joings the id and name with the item_assignees join table (aliased to i_a) on the condition a.id = i_a.assignee_id where the i_a.item_id value equals that of the current item’s ID (item['id']). Then you use the fetchall() method to get the results as a list.

      With the line item = dict(item), you convert the item into a dictionary because a regular sqlite3.Row object does not support assignment, which you will need to add assignees to the item. Next, with the line item['assignees'] = assignees, you add a new key 'assignees' to the item dictionary to access the item’s assignees directly from the item’s dictionary. Then you append the modified item to the items list. You build the list of dictionaries that will hold all of the data; each dictionary key is the to-do list’s title, and its value is a list of all the items that belong to it.

      To print the results, you use the for list_, items in lists.items() loop to go through each to-do list title and the to-do items that belong to it, you print the list’s title (list_), then loop through the to-do items of the list. You added a variable named assignee_names, the value of which uses the join() method to join between the items of the generator expression a['name'] for a in item['assignees'], which extracts the assignee’s name (a['name']), from the data of each assignee in the item['assignees'] list. This joined list of assignee names, you then print with the rest of the to-do item’s data in the print() function.

      Run the list_example.py program:

      Here is the output (with assignees highlighted):

      Output

      Home Buy fruit | id: 2 | done: 0 | assignees: Sammy Cook dinner | id: 3 | done: 0 | assignees: Study Learn Flask | id: 4 | done: 0 | assignees: Learn SQLite | id: 5 | done: 0 | assignees: Work Morning meeting | id: 1 | done: 0 | assignees: Sammy, Jo, Ashley

      You can now display the assignees of each to-do item with the rest of the data.

      You have now displayed the assignee names of each to-do item. Next, you will use this to display the names below each to-do item in the web application’s index page.

      Step 4 — Displaying Assignees in the Index Page

      In this step, you’ll modify the index page of the to-do management application to show the assignees of each to-do item. You will first edit the app.py file, which contains the code for the Flask application, then edit the index.html template file to display the assignees below each to-do item on the index page.

      First, open app.py to edit the index() view function:

      Modify the function to look as follows:

      flask_todo/app.py

      @app.route('/')
      def index():
          conn = get_db_connection()
          todos = conn.execute('SELECT i.id, i.done, i.content, l.title 
                                FROM items i JOIN lists l 
                                ON i.list_id = l.id ORDER BY l.title;').fetchall()
      
          lists = {}
      
          for k, g in groupby(todos, key=lambda t: t['title']):
              # Create an empty list for items
              items = []
              # Go through each to-do item row in the groupby() grouper object
              for item in g:
                  # Get the assignees of the current to-do item
                  assignees = conn.execute('SELECT a.id, a.name FROM assignees a 
                                          JOIN item_assignees i_a 
                                          ON a.id = i_a.assignee_id 
                                          WHERE i_a.item_id = ?',
                                          (item['id'],)).fetchall()
                  # Convert the item row into a dictionary to add assignees
                  item = dict(item)
                  item['assignees'] = assignees
      
                  items.append(item)
      
              # Build the list of dictionaries
              # the list's name (ex: Home/Study/Work) as the key
      
              # and a list of dictionaries of to-do items
              # belonging to that list as the value
              lists[k] = list(items)
      
          conn.close()
          return render_template('index.html', lists=lists)
      

      Save and close the file.

      This is the same code you used in the list_example.py demonstration program in Step 3. With this, the lists variable will contain all the data you need, including assignee data, which you will use to access assignee names in the index.html template file.

      Open the index.html file to add assignee names following each item:

      • nano templates/index.html

      Modify the file to look as follows:

      flask_todo/templates/index.html

      {% extends 'base.html' %}
      
      {% block content %}
          <h1>{% block title %} Welcome to FlaskTodo {% endblock %}</h1>
          {% for list, items in lists.items() %}
              <div class="card" style="width: 18rem; margin-bottom: 50px;">
                  <div class="card-header">
                      <h3>{{ list }}</h3>
                  </div>
                  <ul class="list-group list-group-flush">
                      {% for item in items %}
                          <li class="list-group-item"
                          {% if item['done'] %}
                          style="text-decoration: line-through;"
                          {% endif %}
                          >{{ item['content'] }}
                          {% if not item ['done'] %}
                              {% set URL = 'do' %}
                              {% set BUTTON = 'Do' %}
                          {% else %}
                              {% set URL = 'undo' %}
                              {% set BUTTON = 'Undo' %}
                          {% endif %}
                          <div class="row">
                              <div class="col-12 col-md-3">
                                  <form action="{{ url_for(URL, id=item['id']) }}"
                                      method="POST">
                                      <input type="submit" value="{{ BUTTON }}"
                                          class="btn btn-success btn-sm">
                                  </form>
                              </div>
      
                              <div class="col-12 col-md-3">
                                  <a class="btn btn-warning btn-sm"
                                  href="https://www.digitalocean.com/community/tutorials/{{ url_for("edit', id=item['id']) }}">Edit</a>
                              </div>
      
                              <div class="col-12 col-md-3">
                                  <form action="https://www.digitalocean.com/community/tutorials/{{ url_for("delete', id=item['id']) }}"
                                      method="POST">
                                      <input type="submit" value="Delete"
                                          class="btn btn-danger btn-sm">
                                  </form>
                              </div>
                          </div>
      
                          <hr>
                          {% if item['assignees'] %}
                              <span style="color: #6a6a6a">Assigned to</span>
                              {% for assignee in item['assignees'] %}
                                  <span class="badge badge-primary">
                                      {{ assignee['name'] }}
                                  </span>
                              {% endfor %}
                          {% endif %}
      
                          </li>
                      {% endfor %}
                  </ul>
              </div>
          {% endfor %}
      {% endblock %}
      

      Save and close the file.

      With this modification, you added a line break below each item using the <hr> tag. If the item has any assignees (which you know via the statement if item['assignees']), you display a gray Assigned to text and loop through the item assignees (that is, the item['assignees'] list), and display the assignee name (assignee['name']) in a badge.

      Finally, run the development server:

      Then visit the index page: http://127.0.0.1:5000/.

      Each to-do item can now have many assignees, and you can assign each assignee multiple to-dos. The index page displays all of the items and the assignees of each item.

      Todo Application

      You can access the final code from this repository.

      Conclusion

      In this tutorial, you have learned what a many-to-many relationship is, how to use it in a Flask and SQLite web application, how to join between tables, and how to group relational data in Python.

      You now have a complete to-do application in which users can create new to-do items, mark an item as complete, edit or delete existing items, and create new lists. And each item can be assigned to different assignees.

      To learn more about web development with Python and Flask see these Flask tutorials.



      Source link

      How To Modify Items in a One-to-Many Database Relationships with Flask and SQLite


      The author selected the COVID-19 Relief Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      Flask is a framework for building web applications using the Python language, and SQLite is a database engine that can be used with Python to store application data. In this tutorial, you’ll modify items in an application built using Flask and SQLite with a One-to-Many relationship.

      This tutorial is a continuation of How To Use One-to-Many Database Relationships with Flask and SQLite. After having followed it, you’ve successfully created a Flask application to manage to-do items, organize items in lists, and add new items to the database. In this tutorial, you will add the functionality to mark to-do items as complete, to edit and delete items, and to add new lists to the database. By the end of the tutorial, your application will include edit and delete buttons and strikethroughs for completed to-dos.

      Todo Application

      Prerequisites

      Before you start following this guide, you will need:

      Step 1 — Setting Up the Web Application

      In this step, you will set up the to-do application to be ready for modification. If you followed the tutorial in the prerequisites section and still have the code and the virtual environment in your local machine, you can skip this step.

      First use Git to clone the repository of the previous tutorial’s code:

      • git clone https://github.com/do-community/flask-todo

      Navigate to flask-todo:

      Then create a new virtual environment:

      Activate the environment:

      Install Flask:

      Then, initialize the database using the init_db.py program:

      Next, set the following environment variables:

      • export FLASK_APP=app
      • export FLASK_ENV=development

      FLASK_APP indicates the application you are currently developing, which is app.py in this case. FLASK_ENV specifies the mode—set it to development for development mode, this will allow you to debug the application. (Remember not to use this mode in a production environment.)

      Then run the development server:

      If you go to your browser, you’ll have the application running on the following URL at http://127.0.0.1:5000/.

      To close the development server, use the CTRL + C key combination.

      Next, you will modify the application to add the ability to mark items as complete.

      Step 2 — Marking To-Do Items as Complete

      In this step, you’ll add a button to mark each to-do item as complete.

      To be able to mark items as complete, you’ll add a new column to the items table in your database to have a marker for each item so you know whether it is completed or not, then you will create a new route in your app.py file to change the value of this column depending on the user’s action.

      As a reminder the columns in the items table are currently the following:

      • id: The ID of the item.
      • list_id: The ID of the list the item belongs to.
      • created: The item’s creation date.
      • content: The item’s content.

      First, open schema.sql to modify the items table:

      Add a new column named done to the items table:

      flask_todo/schema.sql

      DROP TABLE IF EXISTS lists;
      DROP TABLE IF EXISTS items;
      
      CREATE TABLE lists (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
          title TEXT NOT NULL
      );
      
      CREATE TABLE items (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          list_id INTEGER NOT NULL,
          created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
          content TEXT NOT NULL,
          done INTEGER NOT NULL DEFAULT 0,
          FOREIGN KEY (list_id) REFERENCES lists (id)
      );
      

      Save and close the file.

      This new column will hold the integer values 0 or 1; the value 0 represents the Boolean value false and 1 represents the value true. The default is 0, which means any new items you add will automatically be unfinished until the user marks the item as complete, in which case the value of the done column will change to 1.

      Then, initialize the database again using the init_db.py program to apply the modifications you have performed on schema.sql:

      Next, open app.py for modification:

      You’ll fetch the id of the item and the value of the done column in the index() function, which fetches the lists and items from the database and sends them to the index.html file for display. The necessary changes to the SQL statement are highlighted in the following file:

      flask_todo/app.py

      @app.route('/')
      def index():
          conn = get_db_connection()
          todos = conn.execute('SELECT i.id, i.done, i.content, l.title 
                                FROM items i JOIN lists l 
                                ON i.list_id = l.id ORDER BY l.title;').fetchall()
      
          lists = {}
      
          for k, g in groupby(todos, key=lambda t: t['title']):
              lists[k] = list(g)
      
          conn.close()
          return render_template('index.html', lists=lists)
      

      Save and close the file.

      With this modification, you get the IDs of the to-do items using i.id and the values of the done column using i.done.

      To understand this change, open list_example.py, which is a small, example program you can use to understand the contents of the database:

      Perform the same modification to the SQL statement as before, then change the last print() function to display the item ID and the value of done:

      flask_todo/list_example.py

      from itertools import groupby
      from app import get_db_connection
      
      conn = get_db_connection()
      
      todos = conn.execute('SELECT i.id, i.done, i.content, l.title 
                            FROM items i JOIN lists l 
                            ON i.list_id = l.id ORDER BY l.title;').fetchall()
      
      lists = {}
      
      for k, g in groupby(todos, key=lambda t: t['title']):
          lists[k] = list(g)
      
      for list_, items in lists.items():
          print(list_)
          for item in items:
              print('    ', item['content'], '| id:',
                    item['id'], '| done:', item['done'])
      

      Save and exit the file.

      Run the example program:

      Here is the output:

      Output

      Home Buy fruit | id: 2 | done: 0 Cook dinner | id: 3 | done: 0 Study Learn Flask | id: 4 | done: 0 Learn SQLite | id: 5 | done: 0 Work Morning meeting | id: 1 | done: 0

      None of the items has been marked as completed so the value of done for each item is 0, which means false. To allow users to change this value and mark items as completed, you will add a new route to the app.py file.

      Open app.py:

      Add a route /do/ at the end of the file:

      flask_todo/app.py

      . . .
      @app.route('/<int:id>/do/', methods=('POST',))
      def do(id):
          conn = get_db_connection()
          conn.execute('UPDATE items SET done = 1 WHERE id = ?', (id,))
          conn.commit()
          conn.close()
          return redirect(url_for('index'))
      

      This new route accepts only POST requests. The do() view function takes an id argument—this is the ID of the item you want to mark as completed. Inside the function, you open a database connection, then you use an UPDATE SQL statement to set the value of the done column to 1 for the item to be marked as completed.

      You use the ? placeholder in the execute() method and pass a tuple containing the ID to safely insert data into the database. Then you commit the transaction and close the connection and redirect to the index page.

      After adding a route to mark items as completed, you need another route to undo this action and return the item to a non-completed status. Add the following route at the end of the file:

      flask_todo/app.py

      . . .
      @app.route('/<int:id>/undo/', methods=('POST',))
      def undo(id):
          conn = get_db_connection()
          conn.execute('UPDATE items SET done = 0 WHERE id = ?', (id,))
          conn.commit()
          conn.close()
          return redirect(url_for('index'))
      

      This route is similar to the /do/ route, and the undo() view function is exactly the same as the do() function except that you set the value of done to 0 instead of 1.

      Save and close the app.py file.

      You now need a button to mark to-do items as completed or uncompleted depending on the state of the item, open the index.html template file:

      • nano templates/index.html

      Change the contents of the inner for loop inside the <ul> element to look as follows:

      flask_todo/templates/index.html

      {% block content %}
          <h1>{% block title %} Welcome to FlaskTodo {% endblock %}</h1>
          {% for list, items in lists.items() %}
              <div class="card" style="width: 18rem; margin-bottom: 50px;">
                  <div class="card-header">
                      <h3>{{ list }}</h3>
                  </div>
                  <ul class="list-group list-group-flush">
                      {% for item in items %}
                          <li class="list-group-item"
                          {% if item['done'] %}
                          style="text-decoration: line-through;"
                          {% endif %}
                          >{{ item['content'] }}
                          {% if not item ['done'] %}
                              {% set URL = 'do' %}
                              {% set BUTTON = 'Do' %}
                          {% else %}
                              {% set URL = 'undo' %}
                              {% set BUTTON = 'Undo' %}
                          {% endif %}
      
      
      
                        <div class="row">
                              <div class="col-12 col-md-3">
                                  <form action="{{ url_for(URL, id=item['id']) }}"
                                      method="POST">
                                      <input type="submit" value="{{ BUTTON }}"
                                          class="btn btn-success btn-sm">
                                  </form>
                              </div>
                          </div>
                          </li>
                      {% endfor %}
                  </ul>
              </div>
          {% endfor %}
      {% endblock %}
      

      In this for loop, you use a line-through CSS value for the text-decoration property if the item is marked as completed, which you know from the value of item['done']. You then use the Jinja syntax set to declare two variables, URL and BUTTON. If the item is not marked as completed the button will have the value Do and the URL will direct to the /do/ route, and if the item was marked as completed, the button will have a value of Undo and will point to /undo/. After, you use both these variables in an input form that submits the proper request depending on the state of the item.

      Run the server:

      You can now mark items as completed on the index page http://127.0.0.1:5000/. Next you will add the ability to edit to-do items.

      Step 3 — Editing To-Do Items

      In this step, you will add a new page for editing items so you can modify the contents of each item and assign items to different lists.

      You will add a new /edit/ route to the app.py file, which will render a new edit.html page in which a user can modify existing items. You will also update the index.html file to add an Edit button to each item.

      First, open the app.py file:

      Then add the following route at the end of the file:

      flask_todo/app.py

      . . .
      @app.route('/<int:id>/edit/', methods=('GET', 'POST'))
      def edit(id):
          conn = get_db_connection()
      
          todo = conn.execute('SELECT i.id, i.list_id, i.done, i.content, l.title 
                               FROM items i JOIN lists l 
                               ON i.list_id = l.id WHERE i.id = ?', (id,)).fetchone()
      
          lists = conn.execute('SELECT title FROM lists;').fetchall()
      
          if request.method == 'POST':
              content = request.form['content']
              list_title = request.form['list']
      
              if not content:
                  flash('Content is required!')
                  return redirect(url_for('edit', id=id))
      
              list_id = conn.execute('SELECT id FROM lists WHERE title = (?);',
                                       (list_title,)).fetchone()['id']
      
              conn.execute('UPDATE items SET content = ?, list_id = ?
                            WHERE id = ?',
                           (content, list_id, id))
              conn.commit()
              conn.close()
              return redirect(url_for('index'))
      
          return render_template('edit.html', todo=todo, lists=lists)
      

      In this new view function, you use the id argument to fetch the ID of the to-do item you want to edit, the ID of the list it belongs to, the value of the done column, the content of the item, and the list title using a SQL JOIN. You save this data in the todo variable. Then you get all of the to-do lists from the database and save them in the lists variable.

      If the request is a normal GET request, the condition if request.method == 'POST' does not run, so the application executes the last render_template() function, passing both todo and lists to an edit.html file.

      If however, a form was submitted, the condition request.method == 'POST' becomes true, in which case you extract the content and the list title the user submitted. If no content was submitted, you flash the message Content is required! and redirect to the same edit page. Otherwise, you fetch the ID of the list the user submitted; this allows the user to move a to-do item from one list to another. Then, you use an UPDATE SQL statement to set the content of the to-do item to the new content the user submitted. You do the same for the list ID. Finally, you commit the changes and close the connection, and redirect the user to the index page.

      Save and close the file.

      To use this new route, you need a new template file called edit.html:

      Add the following contents to this new file:

      flask_todo/templates/edit.html

      {% extends 'base.html' %}
      
      {% block content %}
      
      <h1>{% block title %} Edit an Item {% endblock %}</h1>
      
      <form method="post">
          <div class="form-group">
              <label for="content">Content</label>
              <input type="text" name="content"
                     placeholder="Todo content" class="form-control"
                     value="{{ todo['content'] or request.form['content'] }}"></input>
          </div>
      
          <div class="form-group">
              <label for="list">List</label>
              <select class="form-control" name="list">
                  {% for list in lists %}
                      {% if list['title'] == request.form['list'] %}
                          <option value="{{ request.form['list'] }}" selected>
                              {{ request.form['list'] }}
                          </option>
      
                      {% elif list['title'] == todo['title'] %}
                          <option value="{{ todo['title'] }}" selected>
                              {{ todo['title'] }}
                          </option>
      
                      {% else %}
                          <option value="{{ list['title'] }}">
                              {{ list['title'] }}
                          </option>
                      {% endif %}
                  {% endfor %}
              </select>
          </div>
          <div class="form-group">
              <button type="submit" class="btn btn-primary">Submit</button>
          </div>
      </form>
      {% endblock %}
      

      You use the value {{ todo['content'] or request.form['content'] }} for the content input. This signifies that the value will be either the current content of the to-do item or what the user has submitted in a failed attempt to submit the form.

      For the list selection form, you loop through the lists variable, and if the list title is the same as the one stored in the request.form object (from a failed attempt), then set that list title as the selected value. Otherwise if the list title equals the one stored in the todo variable, then set it as the selected value. This is the current list title of the to-do item before any modification; the rest of the options are then displayed without the selected attribute.

      Save and close the file.

      Then, open index.html to add an Edit button:

      • nano templates/index.html

      Change the contents of the div tag with the "row" class to add another column as follows:

      flask_todo/templates/index.html

      . . .
      <div class="row">
          <div class="col-12 col-md-3">
              <form action="{{ url_for(URL, id=item['id']) }}"
                  method="POST">
                  <input type="submit" value="{{ BUTTON }}"
                      class="btn btn-success btn-sm">
              </form>
          </div>
          <div class="col-12 col-md-3">
              <a class="btn btn-warning btn-sm"
              href="https://www.digitalocean.com/community/tutorials/{{ url_for("edit', id=item['id']) }}">Edit</a>
          </div>
      </div>
      

      Save and close the file.

      This is a standard <a> link tag that points to the relevant /edit/ route for each item.

      Run the server if you haven’t already:

      You can now go to the index page http://127.0.0.1:5000/ and experiment with modifying to-do items. In the next step, you will add a button to delete items.

      Step 4 — Deleting To-Do Items

      In this step, you will add the ability to delete specific to-do items.

      You will first need to add a new /delete/ route, open app.py:

      Then add the following route at the end of the file:

      flask_todo/app.py

      . . .
      @app.route('/<int:id>/delete/', methods=('POST',))
      def delete(id):
          conn = get_db_connection()
          conn.execute('DELETE FROM items WHERE id = ?', (id,))
          conn.commit()
          conn.close()
          return redirect(url_for('index'))
      

      Save and close the file.

      The delete() view function accepts an id argument. When a POST request gets sent, you use the DELETE SQL statement to delete the item with the matching id value, then you commit the transaction and close the database connection, and return to the index page.

      Next, open templates/index.html to add a Delete button:

      • nano templates/index.html

      Add the following highlighted div tag below the Edit button:

      flask_todo/templates/index.html

      <div class="row">
          <div class="col-12 col-md-3">
              <form action="{{ url_for(URL, id=item['id']) }}"
                  method="POST">
                  <input type="submit" value="{{ BUTTON }}"
                      class="btn btn-success btn-sm">
              </form>
          </div>
      
          <div class="col-12 col-md-3">
              <a class="btn btn-warning btn-sm"
              href="https://www.digitalocean.com/community/tutorials/{{ url_for("edit', id=item['id']) }}">Edit</a>
          </div>
      
          <div class="col-12 col-md-3">
              <form action="https://www.digitalocean.com/community/tutorials/{{ url_for("delete', id=item['id']) }}"
                  method="POST">
                  <input type="submit" value="Delete"
                      class="btn btn-danger btn-sm">
              </form>
          </div>
      </div>
      

      This new submit button sends a POST request to the /delete/ route for each item.

      Save and close the file.

      Then run the development server:

      Go to the index page and try out the new Delete button—you can now delete any item you want.

      Now that you have added the ability to delete existing to-do items, you will move on to add the ability to add new lists in the next step.

      Step 5 — Adding New Lists

      So far, lists can only be added directly from the database. In this step, you will add the ability to create new lists when the user adds a new item, instead of only choosing between the existing lists. You will incorporate a new option called New List, which when chosen, the user can input the name of the new list they wish to create.

      First, open app.py:

      Then, modify the create() view function by adding the following highlighted lines to the if request.method == 'POST' condition:

      flask_todo/app.py

      . . .
      @app.route('/create/', methods=('GET', 'POST'))
      def create():
          conn = get_db_connection()
      
          if request.method == 'POST':
              content = request.form['content']
              list_title = request.form['list']
      
              new_list = request.form['new_list']
      
              # If a new list title is submitted, add it to the database
              if list_title == 'New List' and new_list:
                  conn.execute('INSERT INTO lists (title) VALUES (?)',
                               (new_list,))
                  conn.commit()
                  # Update list_title to refer to the newly added list
                  list_title = new_list
      
              if not content:
                  flash('Content is required!')
                  return redirect(url_for('index'))
      
              list_id = conn.execute('SELECT id FROM lists WHERE title = (?);',
                                       (list_title,)).fetchone()['id']
              conn.execute('INSERT INTO items (content, list_id) VALUES (?, ?)',
                           (content, list_id))
              conn.commit()
              conn.close()
              return redirect(url_for('index'))
      
          lists = conn.execute('SELECT title FROM lists;').fetchall()
      
          conn.close()
          return render_template('create.html', lists=lists)
      

      Save and close the file.

      Here you save the value of a new form field called new_list in a variable. You will add this field later to the create.html file. Next, in the list_title == 'New List' and new_list condition, you check whether the list_title has the value 'New List', which indicates that the user wishes to create a new list. You also check that the value of the new_list variable is not None, if this condition is met, you use an INSERT INTO SQL statement to add the newly submitted list title to the lists table. You commit the transaction, then you update the value of the list_title variable to match that of the newly added list for later use.

      Next, open create.html to add a new <option> tag to let the user add a new list:

      • nano templates/create.html

      Modify the file by adding the highlighted tags in the following code:

      flask_todo/templates/create.html

          <div class="form-group">
              <label for="list">List</label>
              <select class="form-control" name="list">
                  <option value="New List" selected>New List</option>
                  {% for list in lists %}
                      {% if list['title'] == request.form['list'] %}
                          <option value="{{ request.form['list'] }}" selected>
                              {{ request.form['list'] }}
                          </option>
                      {% else %}
                          <option value="{{ list['title'] }}">
                              {{ list['title'] }}
                          </option>
                      {% endif %}
                  {% endfor %}
              </select>
          </div>
      
          <div class="form-group">
              <label for="new_list">New List</label>
              <input type="text" name="new_list"
                      placeholder="New list name" class="form-control"
                      value="{{ request.form['new_list'] }}"></input>
          </div>
      
          <div class="form-group">
              <button type="submit" class="btn btn-primary">Submit</button>
          </div>
      

      Save and close the file.

      You have added a new <option> tag to refer to the New List option, this will allow the user to specify that they want to create a new list. Then you add another <div> with an input field named new_list, this field is where the user will input the title of the new list they wish to create.

      Finally, run the development server:

      Then visit the index page:

      http://127.0.0.1:5000/
      

      The application will now look as follows:

      Todo Application

      With the new additions to your application, users can now mark to-do items as complete or restore completed items to a non-completion state, edit and delete existing items, and create new lists for different kinds of to-do tasks.

      You can browse the full source code of the application in the DigitalOcean Community Repository.

      Conclusion

      You now have a complete to-do application in which users can create new to-do items, mark an item as complete, and edit or delete existing items, in addition to the ability to create new lists. You have modified a Flask web application, added new features to it, and modified database items specifically in a One-to-Many relationship. You may develop this application further by learning How To Add Authentication to Your App with Flask-Login to add security to your Flask application.



      Source link