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.pathorhomeif the path is/today: todays date (datetime.today())request_path:self.request.path
Tip
To add extra context we can use the BASE_MIXIN_CONTEXT_PLUGIN
plugin system.
For an example of this, see Views
(from the apps app).
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:
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
Tip
The code for the Zebra Datepicker is included in our base.html
template.
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, then
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_idwhich specifies the
idof the drop area of your page (the default isfiledrop-zone). unless usingRequiredFieldFormthis must be specified for each subsequentFileDropWidgetas thezone_idmust be unique.default_textwhich specifies the text to be displayed when there is no file dropped (default is Drop a file…)
click_textthe 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
FileDropWidget 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 fields to pure-input-1 e.g.
self.fields[name].widget.attrs.update({"class": "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'})
Google Analytics
To add the site tag to a site, add google_site_tag to the context and:
{% include 'base/_google_site_tag.html' %}
Management Commands
test-connection-postgres
For local connections:
python manage.py test-connection-postgres -name www_hatherleigh_info -user patrick
For remote connections:
# Windows
python manage.py test-connection-portgres -host 10.3.1.7 -name www_hatherleigh_info -port 5432 -pass my-pass -user patrick
# Linux
python manage.py test-connection-postgres -host localhost -port 5432 -name www_hatherleigh_info -user patrick -pass my-pass
Tip
Source code is in
base/management/commands/test-connection-postgres.py.
test-connection-redis
Connect via localhost:6379:
python manage.py test-connection-redis
To see a fail (e.g. change the port):
python manage.py test-connection-redis -host localhost -port 6380
URL:
python manage.py test-connection-redis -url "redis://localhost:6379?decode_responses=True&health_check_interval=2"
SSL URL:
python manage.py test-connection-redis -url "rediss://localhost:6666?ssl_cert_reqs=none&decode_responses=True&health_check_interval=2"
Models
RetryModel
This model will handle retry operations e.g. sending an email.
You can see an example of this in the Message model in the mattermost
app:
https://gitlab.com/kb/mattermost/blob/master/mattermost/models.py#L109
Model requirements are as follows:
Use a model manager inherited from
RetryModelManager.Create a
DEFAULT_MAX_RETRY_COUNT(in your model inherited fromRetryModel).
DEFAULT_MAX_RETRY_COUNT = 5
The DEFAULT_MAX_RETRY_COUNT can be used when creating an instance of your
model (perhaps in a create_… method in your model manager)e.g:
max_retry_count=self.model.DEFAULT_MAX_RETRY_COUNT,
Create a
processmethod (will be called by theprocessmethod inRetryModelManager). This method must returnTruefor success andFalsefor fail e.g.
def process(self):
"""Process the message.
.. note:: This method is running inside a transaction.
This method is called by ``RetryModelManager``
(see ``base/model_utils.py``).
"""
result = False
response = requests.post(self.channel.url)
if HTTPStatus.CREATED == response.status_code:
result = True
else:
logger.error("Cannot post message to Mattermost")
return result
Model manager requirements are as follows:
Create a
currentmethod which can simple returnallrows.
def current(self):
return self.model.objects.exclude(deleted=True)
TimedCreateModifyDeleteModel
Warning
We don’t delete data (unless there is a specific requirement for it).
The TimedCreateModifyDeleteModel has set_deleted, is_deleted and
undelete methods. To use the class:
class ContactManager(models.Manager):
def current(self):
return self.model.objects.exclude(deleted=True)
class Contact(TimedCreateModifyDeleteModel):
# ...
objects = ContactManager()
Tip
To mark an object as deleted in a Django view, see UpdateView not DeleteView
Pagination
Tip
For basic Django pagination, see Pagination…
If you have a GET form (search or similar) on your view, then you will want
to include the URL parameters with the page number:
{% include 'base/_paginate_with_parameters.html' %}
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
get_parameters = self.request.GET.copy()
if self.page_kwarg in get_parameters:
del get_parameters[self.page_kwarg]
context.update(
dict(
get_parameters=get_parameters.urlencode(),
)
)
return context
Tip
Source code in _paginate_with_parameters.html.
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 django.urls import reverse
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
To escape the parameters (make sure the string is safe), try:
from django.utils.html import escape
payment_details = escape(self.request.GET.get("payment_details"))
Tip
This may not be a good way to do this… (PK 12/01/2023)
Standard
We have a few standard URLs:
logoutloginproject.homethe home page of the web site.project.dashthe 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.project.taskslist of tasks for the Workflow appweb.contactthe contact / enquiry page of the web site. This is used by the GDPR unsubscribe page.