Quick reminder what is frisor

Frisor is a web application in Python/Django which is just a box for interesting urls. When using untrusted network I don’t want to login anywhere to save interesting content I found. More about this is here: Introduction to frisor.

This week I did mostly refactoring - I introduced Django Forms instead of rendering form by hand (it means I rewrote all logic for views). I added some Django library for managing bootstrap styles (actually picking one was really painful) - error management improved because of that. All my html templates are rewritten.

My github repo: firsor repo.

Currently successfully added url in my application looks like this:

screenshot

Forms in Django

Usage of forms on web pages is so popular, that in Django there is a special mechanism for them. It allows to add a form as a part of context to template - then render it automatically, so a programmer doesn’t have to write a html code. To start with forms it’s necessary to create forms.py file and create a class which derives from django.forms.Form. For a simple form without custom validation it’s enough to create a form fields in this class, which correspond to fields desired in html form. Here is an example of my form for urls:

    # forms.py
    from django import forms


    class UrlForm(forms.Form):
        url = forms.URLField(label='URL')
        title = forms.CharField(label='Title')
        nick = forms.CharField(label='Nick')

Next in views.py if you use method views you need to create an instance of this form and add it to view context. Class views for forms I covered in later part of this post.

    # views.py
    # def index():
    # ...
        form = UrlForm()
        context = {
            'url_list': url_list,
            'form': form
        }
        return render(request, 'index.html', context)

After that we can use this form in html templates in this way:

    <form method="post">
        {% csrf_token %}
        {{ form }}
        <input type="submit" value="Submit" />
    </form>

But there is one issue with that - this form again looks very bad… All my bootstrap styles at my form are gone!

screenshot

Bootstraping Django Forms

There are a few solutions on how to style a form which was created using Django Forms mechanism.

Libraries

There are plenty libraries which can help with adding bootstrap to templates or forms:

  • django-bootstrap3
  • django-bootstrap-form
  • django-forms-bootstrap
  • django-crispy-forms

Actually it seems that names of these libraries were generated by some Django names generator. It’s hard to see a difference between django-bootstrap-form and django-forms-bootstrap… Unfortunately, I didn’t find forms-django-bootstrap - maybe I should write it myself.

These libraries are quite similar - they all add new custom bootstrap filters to templates. All of them have to be loaded into template, for example: {% load bootstrap3 %} , to use form rendered with bootstrap styles: {% bootstrap_form form %} or {{form | bootstrap }} . And for all of them it looks the same - it’s still the same bootstrap!

Anyway in my frisor project I decided to use django-bootstrap3, because this one is not about forms only. Still the documentation of this framework sucks - for example they forgot to mention that to use it, it should be put into settings.py in INSTALLED_APPS as bootstrap3.

It’s really painful that there is no naming convention for Django packages and application names. Actually application name can be so different than package name - you are on your own there…

Styling in Django Forms class

It’s possible to put styles into forms class. For me it’s quite bad design, because a form should be just a model of data and it shouldn’t have anything to do with presentation of it. It’s the same as putting some business logic into ORM model class.

Anyway here is an example:

    
    # models.py
    from django import forms


    class UrlForm(forms.Form):
        url = forms.URLField(label='URL')
        title = forms.CharField(widget=forms.TextInput(attrs={'class': 'form-control'}),
                                label='Title')
    

There is a possibility to add html attributes to widget field of model field constructor. Also there are other ways to achieve this: by using Meta class in form class. Meta classes deserve more attention, so I’ll leave them for feature posts.

Styling in html templates

There are two ways: writing own filter which will add a css class to a field or simply putting all necessary styles into html form by hand (it means you don’t have to add a form to context when rendering view). I’ll not cover them more - first is about custom filters in templates for what I want to write a separate post. Second way I’ve used previous week.

Class view and forms in Django

Actually I was a bit surprised that Django tutorial didn’t mention that it’s possible to have a class views in views.py. Actually I’ve read that views.py with methods instead of classes is quite bad design. So I refactored my views.py to use class views - for a form for Urls I’ve a class UrlForm which derives from django.views.generic.FormView. I’ve implemented two methods from it - get_context_data is for managing context for all requests, form_valid(form) for managing request from form. My new views.py looks like this:

    # views.py
    import logging

    from django.contrib import messages
    from django.shortcuts import render
    from django.views.generic import FormView

    from .models import Url
    from .forms import UrlForm

    logger = logging.getLogger(__name__)


    class UrlView(FormView):

        form_class = UrlForm
        template_name = 'index.html'
        success_url = '/'

        def get_context_data(self, **kwargs):
            context = super().get_context_data()
            url_list = Url.objects.order_by('-publish_date').all()
            context['url_list'] = url_list
            return context

        def form_valid(self, form):
            context = self.get_context_data()
            context['form'] = form

            title = self.request.POST.get('title')
            nick = self.request.POST.get('nick')
            url = self.request.POST.get('url')

            db_url = Url.create(url=url, title=title, nick=nick)
            db_url.save()

            messages.success(self.request, 'Your new url: %s' % url,
                             extra_tags='safe')
            return render(self.request, 'index.html', context)

Method form_valid does the saving url logic as before I did in my index method. Method get_context_data is responsible for getting all urls from database and put them into context to render on my index.html template. To render an index.html template properly you need to point to a class view using as_view method:

    urlpatterns = [
        url(r'^$', UrlView.as_view())
        ]

In my views.py there are messages - they allow to display error or success messages in a nice way:

    <!-- index.html -->
    {% for message in messages %}
        <div class="{{ message|bootstrap_message_classes }} alert-dismissable">
            <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&#215;</button>
            {{ message|safe }}
        </div>
    {% endfor %}

and they are shown like this:

screenshot

My TODO list:

  1. Introduce unit tests.
  2. Add comments and tags to urls.
  3. Refactor database logic from views.py to use some service.py.
  4. Fix issue with not cleared form after submit.
  5. Read about WebSockets to introduce chat mechanism.

Leave a Comment