Let's make a HTML form with Python

Let's make a HTML form with Python

Yattag practice

·

8 min read

It's about time to practice our Yattag skills and start generating HTML.

What will we make? This form(here's the HTML code).

python-form.jpeg

Looks complex enough so we can get some good practice building it.


We already have our Yattag library installed so we can get to work.

Firstly, let's import what we need:

from yattag import Doc

Looking at our final product, we notice that we have some tag nodes containing only text. We can use the line method to write those.

doc, tag, text, line = Doc(
    defaults= {
        'required': 'True',
        'klass': 'item',
    }
).ttl()

We also decide to define some defaults, as all fields except the 'Phone' are required and there seem to be a lot of fields with the same characteristics.

Next we need to structure our form:

doc.asis('<!DOCTYPE html>')
with tag('html'):
    with tag('head'):
        line('title', 'Gym Membership Form')
        doc.stag('link', rel='stylesheet', href='https://fonts.googleapis.com/css?family=Roboto:300,400,500,700')
        doc.stag('link', rel='stylesheet', href='https://use.fontawesome.com/releases/v5.5.0/css/all.css" integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU')
        doc.stag('link', rel='stylesheet', href='style.css')
    with tag('body'):
        with tag('div', klass='container'):
            with tag('form', action='/'):
                with tag('div', klass='banner'):
                    line('h1', 'Gym Membership Form')

We need the asis method for the `<!DOCTYPE html>' tag.

Then we add the html, head and body tags.

We decide to keep the css in a separate file and we use the stag method to bring it in.

html, body {
    min-height: 100%;
}

body, div, form, input, select, p { 
    padding: 0;
    margin: 0;
    outline: none;
    font-family: Roboto, Arial, sans-serif;
    font-size: 14px;
    color: #666;
    line-height: 22px;
}

h1 {
    position: absolute;
    margin: 0;
    font-size: 36px;
    color: #fff;
    z-index: 2;
}

span.required {
    color: red;
}

.container {
    display: flex;
    justify-content: center;
    align-items: center;
    height: inherit;
    padding: 20px;
}

form {
    width: 100%;
    padding: 20px;
    border-radius: 6px;
    background: #fff;
    box-shadow: 0 0 30px 0 #095484; 
}

.banner {
    position: relative;
    height: 180px;
    background-image: url("gym.png");  
    background-size: cover;
    display: flex;
    justify-content: center;
    align-items: center;
    text-align: center;
}

.banner::after {
    content: "";
    background-color: rgba(0, 0, 0, 0.4); 
    position: absolute;
    width: 100%;
    height: 100%;
}

p.top-info {
    margin: 10px 0;
}

input, select {
    margin-bottom: 10px;
    border: 1px solid #ccc;
    border-radius: 3px;
}

input {
    width: calc(100% - 10px);
    padding: 5px;
}

select {
    width: 100%;
    padding: 7px 0;
    background: transparent;
}

.item:hover p, .question:hover p, .question label:hover, input:hover::placeholder {
    color: #095484;
}

.item input:hover, .item select:hover {
    border: 1px solid transparent;
    box-shadow: 0 0 5px 0 #095484;
    color: #095484;
}

.item {
    position: relative;
    margin: 10px 0;
}

.question input {
    width: auto;
    margin: 0;
    border-radius: 50%;
}

.question input, .question span {
    vertical-align: middle;
}

.question label {
    display: inline-block;
    margin: 5px 20px 15px 0;
}

.btn-block {
    margin-top: 10px;
    text-align: center;
}

button {
    width: 150px;
    padding: 10px;
    border: none;
    border-radius: 5px; 
    background: #095484;
    font-size: 16px;
    color: #fff;
    cursor: pointer;
}

button:hover {
    background: #0666a3;
}

@media (min-width: 568px) {
    .name-item, .city-item {
        display: flex;
        flex-wrap: wrap;
        justify-content: space-between;
    }
    .name-item input, .city-item input {
      width: calc(50% - 20px);
    }
    .city-item select {
      width: calc(50% - 8px);
    }
}

Now that we have something in our form, we can write it on a file to check if our script works as intended:

html_file = doc.getvalue()
with open('index.html', 'w') as f:
    f.write(html_file)

And it looks that our script is working fine. Good job! Let's write some more.

with tag('form',action='/'):
                with tag('div', klass='banner'):
                    line('h1', 'Gym Membership Form')
                with tag('p', klass='top-info'):
                    text('Want to become a member of our Gym? Then start by filling our form to complete registration. We will contact you shortly to notify you about your membership card.')
                with tag('div'):
                    with tag('p'):
                        text('Name')
                        with tag('span', klass='required'):
                            text('*')
                    with tag('div', klass='name-item'):
                        doc.input(type='text', name='name', placeholder='First')
                        doc.input(type='text', name='name', placeholder='Last')

We use the doc.input method as a shortcut instead of with tag('input').

We don't need to specify required in the input field as we already have it as a default. However, we do need to specify klass=required for the span tag to style the asterisk with the red color.

We notice the input fields are pretty much the same, except for the placeholder. We could write a for loop and have

for data in ['First', 'Last']:
      doc.input(type='text', name='name', placeholder=data)

We try it with the next ones:

with tag('div', klass='question'):
                        with tag('p'):
                            text('Gender')
                            with tag('span', klass='required'):
                                text('*')
                        with tag('div', klass='question-answer'):
                            for data in ['Male', 'Female']:
                                with tag('label'):
                                    doc.input(type='radio', value='none', name='gender', required='True')
                                    line('span', data)
                    for data in ['Your current weight', 'Desired weight', 'Height', 'BMI']:
                        with tag('div'):
                            with tag('p'): 
                                text(data)
                                with tag('span', klass='required'):
                                    text('*')
                                    doc.input(type='text', name='name')

We use a for loop for our options in the select tab, too.

with tag('div'):
                        with tag('p'):
                            text('Home Address')
                            with tag('span', klass='required'):
                                 text('*')
                            doc.input(type='text', name='name', placeholder='Street Address')
                        for data in ['City', 'Region', 'Postal/Zip code']:
                            doc.input(type='text', name='name', placeholder=data)
                        with doc.select(name='Country'):
                            for value, description in (
                                ('1', 'Russia'),
                                ('2', 'Germany'),
                                ('3', 'France'),
                                ('4', 'Armenia'),
                                ('5', 'USA')
                            ):
                                with doc.option(value=value):
                                    text(description)

We specify required='False' for the 'Phone' field, to make it not required:

with tag('div'):
                        with tag('p'):
                            text('Email')
                            with tag('span', klass='required'):
                                text('*')
                        doc.input(type='text', name='name')
                    with tag('div'):
                        line('p', 'Phone')
                        doc.input(type='text', name='name', required='False')
                    with tag('div', klass='question'):
                        with tag('p'):
                            text('Do you require a personal trainer?')
                            with tag('span', klass='required'):
                                text('*')
                        with tag('div', klass='question-aswer'):
                            for option in ['Yes', 'No']:
                                with tag('label'):
                                    doc.input(type='radio', value='none', name='personal-trainer')
                                    line('span', option)
                    with tag('div', klass='question'):
                        with tag('p'):
                            text('Have you been in a Gym before?')
                            with tag('span', klass='required'):
                                text('*')
                        with tag('div', klass='question-aswer'):
                            for option in ['Yes', 'No']:
                                with tag('label'):
                                    doc.input(type='radio', value='none', name='personal-trainer')
                                    line('span', option)
                    with tag('div', klass='question'):
                        with tag('p'):
                            text('Membership Type')
                            with tag('span', klass='required'):
                                text('*')
                        with tag('div', klass='question-aswer'):
                            for option in ['Regular', 'Pro', 'VIP']:
                                with tag('label'):
                                    doc.input(type='radio', value='none', name='personal-trainer')
                                    line('span', option)
                    with tag('div', klass='btn-block'):
                        with tag('button', type='submit', href='/'):
                            text('Apply')

And that's all! We're done. Our form looks exactly as we wanted.

Now we have to think how to make it functional. We'll have to find another Python library for that, so we decide to let it for another time.