base

https://gitlab.com/kb/base

BaseMixin

https://gitlab.com/kb/base/blob/master/base/view_utils.py

The BaseMixin class adds the following to the template context:

  • path: self.request.path or home if the path is /
  • today: todays date (datetime.today())
  • request_path: self.request.path

Bullet (Hide)

If you create a form with a RadioSelect widget e.g:

send_email = forms.ChoiceField(widget=forms.RadioSelect, choices=CHOICES)

Then you can hide the bullets on the page by using the kb-hide-bullet style e.g:

{% include '_form.html' with form_class='kb-hide-bullet' %}

Checkbox

We have (very nice) inline checkboxes:

_images/inline-checkbox.png

To make checkbox field and label appear on a single line add:

{% include '_form.html' with inline_checkbox=True ... %}

Tip

Don’t miss the nice formatting for Forms

For more information, take a look at the documentation in the templates:

Date Picker

Using RequiredFieldForm will automatically set date fields to use the zebra datepicker control e.g:

# forms.py
from base.form_utils import RequiredFieldForm

class EventForm(RequiredFieldForm):

Warning

If your date control isn’t working as a date picker, then check your form code to see if you call self.fields[name].widget.attrs.update({'class'... on the field. This will overwrite the update done by the __init__ method on RequiredFieldForm.

File and Image Upload

If you include the _form.html template and you want to upload files, the add the multipart option:

{% include '_form.html' with multipart=True %}

FileDropInput Widget

To display a drag and drop file upload, set the widget for that field to FileDropInput. If your form inherits from RequiredFieldForm all FileField and ImageField fields will automatically use the FileDropInput widget. The zone_id for the first field will be filedrop-zone and filedrop-1-zone for the second, filedrop-2-zone for the third etc.

For example, assuming the following model is defined in your models.py:

class Document(TimedCreateModifyDeleteModel):
    file = models.FileField(upload_to='document')
    preview = models.FileField(upload_to='image')
    description = models.CharField(max_length=256)

    class Meta:
        verbose_name = 'Document'
        verbose_name_plural = 'Documents'

    def __str__(self):
        return '{}: {}'.format(self.file, self.description)

You can define a model form called DocumentForm as follows:

from django import forms
from base.form_utils import FileDropInput
from .models import Document

class DocumentForm(models.ModelForm):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for name in ('file', 'description'):
            self.fields[name].widget.attrs.update(
                {'class': 'pure-input-2-3'}
            )

    class Meta:
        model = Document
        fields = (
            'file',
            'preview',
            'description',
        )
        widgets = {
            'file': FileDropInput()
            'preview': FileDropInput(
                zone_id="filedrop-1-zone",
                default_text="Optional text to replace 'Drop a file ...'"
                click_text="Optional text to replace 'or click here...'"
            )
        }

or using RequiredFieldForm (which configures the each widget with the appropriate zone_id) as follows:

from base.form_utils import RequiredFieldForm
from .models import Document

class DocumentForm(RequiredFieldForm):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        for name in ('file', 'preview', 'description'):
            self.fields[name].widget.attrs.update(
                {'class': 'pure-input-2-3'}
            )

    class Meta:
        model = Document
        fields = (
            'file',
            'preview',
            'description',
        )

When creating a FileDropWidget you can optionally pass three parameters:

``zone_id`` which specifies the ``id`` of the drop area of your page (the
           default is ``filedrop-zone``). unless using RequiredFieldForm
           this must be specified for each subsequent FileDropWidget as the
           zone_id must be unique.

``default_text`` which specifies the text to be displayed when there is no
            file dropped (default is *Drop a file...*)

``click_text``.  the text a button that allows choosing a file from the file
            system (default is *or click here*)

If you’re inheriting from RequiredFieldForm you can change the values for zone_id, default_text and click_text in the form’s __init__ using either of the following:

  • Update the attrs member of the widget as follows:
self.fields['preview'].widget.attrs.update({
    'default_text': "Drop a preview image file...",
    'click_text': "or click here to choose one"
})
  • Or specify a new widget:
self.fields['preview'].widget = FileDropInput(
    zone_id="filedrop-1-zone",
    default_text="Drop a preview image file...",
    click_text="or click here to choose one"
)

See the example_base app File Drop Demo for an example form with two FileFields. See the code in example_base/forms.py

Using FileDropWidget on your page

filedrop.css defines a pure-css style appearance of a FileDropWidget for the standard zone_ids (these are filedrop-zone, filedrop-1-zone, filedrop-2-zone and filedrop-3-zone). If your page inherits from the base app’s base.html then this is already included. Otherwise include it on your page with the code:

<link rel="stylesheet" type="text/css" href="{% static 'base/css/filedrop.css' %}">

It probably makes sense to include this above your project css file as this allows the styles to be overidden.

filedrop.js initialises the filedrop zones created when a FileDropWidget is rendered for the standard zone_ids.

If your template does not inherit from the base app’s base.html, include this on your page with the code:

<script src="{% static 'base/js/filedrop.js' %}"></script>

Including the script at the end of the page ensures that DOM is loaded.

Advanced control of FileDropWidget

If you want to change the appearance of your FileDropWidget you will need to create css rules for some or all of the following selectors:

#filedrop-zone
#filedrop-zone .filedrop-file-name
#filedrop-zone .filedrop-click-here

// please note if you have more than one FileDropWidget on your page you
// will need to define additional selectors you can of course group your
// selectors to get a similar appearance for each widget.

If you want to use a different zone_id in your form or add more than 4 FileDropWidgets to your page then in addition to creating a style as above you must also call dropZoneManager with the zone_id of each non standard zone_id as follows:

<!-- insert after filedrop.js on you page -->
<script>
  dropZoneManager("non-standard-zone-id");
</script>

This should appear below filezone.js on your page.

Flash of Unstyled Content (FOUC)

From An Accessible Way to Stop Your Content From Flashing (FOUC)

Include our _fouc.html template:

{% block content %}
  {% include 'base/_fouc.html' %}

Add the stuffIDontWantToFlash style to any elements you want to hide on the initial render:

<div class="pure-u-1 stuffIDontWantToFlash">
  <div id="tree"></div>
</div>

Before working with the element, remove the style (to make it visible):

function toggleFolderTree(event) {
  $(".stuffIDontWantToFlash").removeClass("stuffIDontWantToFlash");
  $("#folderTree").slideToggle();

Forms

Label

To hide the : character on a form, set the first parameter on the model field to " " e.g:

address_two = models.CharField(" ", max_length=100, blank=True)

This sets the verbose_name for the field (Django, Verbose field names).

The _form_field.html template checks to see if the verbose_name is a space (field.label != " ") and hides the : character if it is.

Aligned

To create an aligned form that displays well on both desktop and mobile use this markup:

<div class="pure-g">
  <div class="pure-u-1 pure-u-lg-2-3">
    {% include '_form.html' with legend='Aligned Form' multipart=True inline_checkbox=True aligned=True %}
  </div>
</div>

Note

in forms.py set the class for the form to pure-input-1.

The aligned parameter is set to true so the label and the field will be displayed on the same line and any help text will be displayed below the field. Your template should include both the pure-min.0.6.0.css and base.css stylesheets. The inline_checkbox parameter is explained above see Checkbox

Stacked

A Stacked form can be created using this technique too:

<div class="pure-g">
  <div class="pure-u-1 pure-u-lg-2-3">
    {% include '_form.html' with legend='Stacked Form' multipart=True inline_checkbox=True %}
  </div>
</div>

The purecss class pure-u-lg-2-3 will display the form on two thirds of a large screen and on smaller screens the pure-u-1 class will allocate ~100%

In forms.py the class for the input field should be set as pure-input-1 which is a pure class that sets the width of the field to 100%. For aligned forms base.css modifies pure-input-1 width to calc(100% - 12rem) (12rem is the width of a the label on an aligned form). A sample form is shown below:

from django import forms

class CoolForm(forms.Form):

    repeat = forms.ChoiceField(choices=((0, 'Weekly'), (1, 'Monthly'), ))
    times = forms.IntegerField()
    reason = models.CharField(max_length=256)
    all_day = forms.BooleanField(required=False)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for name in ("repeat", "times", "reason", ):
            self.fields[name].widget.attrs.update({'class': 'pure-input-1'})

PDFObject

In the HTML template:

<div id="pdf-viewer"></div>

At the end of the template:

{% block script_extra %}
  {{ block.super }}
  {% include 'base/_pdfobject.html' %}
  <script>PDFObject.embed("{% url 'document.download' document.pk %}", "#pdf-viewer");</script>
  <style>
    .pdfobject-container { height: 800px;}
    .pdfobject { border: 1px solid #666; }
  </style>
{% endblock script_extra %}

Note

If you get Failed to load PDF document in Google Chrome, see PDFObject

RedirectNextMixin

from base.view_utils import BaseMixin, RedirectNextMixin

Note

This example is for use with an update view and the POST method.

Add the request context processor to settings:

'context_processors': [
    # ...
    'django.template.context_processors.request',

In the calling template:

<a href="{% url 'dash.document.update' document.pk %}?next={{ request.path }}" class="pure-menu-link">

Tip

If your URL includes extra parameters, then try the following (from Stack Exchange, Django next with query parameters):

<a href="{% url 'dash.document.update' document.pk %}?next={{request.get_full_path|urlencode}}" class="pure-menu-link">

In the view, add the RedirectNextMixin:

from django.contrib.auth import REDIRECT_FIELD_NAME
from base.view_utils import BaseMixin, RedirectNextMixin

class ContactDetailView(
    LoginRequiredMixin, StaffuserRequiredMixin,
    RedirectNextMixin, BaseMixin, DetailView):

If required, add a get_success_url method:

def get_success_url(self):
    next_url = self.request.POST.get(REDIRECT_FIELD_NAME)
    if next_url:
        return next_url
    else:
        return reverse('dash.document.detail', args=[self.object.pk])

Our standard _form.html template includes this section:

  {% if next %}
    <input type="hidden" name="next" value="{{ next }}" />
  {% endif %}
</form>

In the menu of the form template:

<li class="pure-menu-item">
  {% if next %}
    <a href="{{ next }}" class="pure-menu-link">
      <i class="fa fa-reply"></i>
    </a>
  {% else %}
    <a href="{% url 'project.settings' %}" class="pure-menu-link">
      <i class="fa fa-reply"></i>
      Settings
    </a>
  {% endif %}
</li>

RequiredFieldForm

https://gitlab.com/kb/base/blob/master/base/form_utils.py

e.g:

class SnippetForm(RequiredFieldForm):

URL

Parameters

from base.url_utils import url_with_querystring
url = url_with_querystring(
    reverse(order_add),
    responsible=employee.id,
    scheduled_for=datetime.date.today(),
)
>>> http://localhost/order/add/?responsible=5&scheduled_for=2011-03-17

Standard

We have a few standard URLs:

  • logout
  • login
  • project.home the home page of the web site.
  • project.dash the home page for a member of staff (or logged in user if the project requires it).
  • project.settings, the project settings. Usually only accessible to a member of staff.