As OSM moves forward and grows as a reputable design agency, it’s imperative to make sure as a team we are consistently producing work that makes us unique and stand out from the crowd. This styleguide will help developers achieve consistency and the highest standard of work as project size and frequency increase.
As the projects we work on become larger in scale and longer-running, it is very important that we work in a unified way and with a ever growing team with different specialities and abilities. It is necessary to keep the process streamlined in order to:
- Keep everything maintainable.
- Keep code transparent, sane, and readable.
- Keep everything scalable.
For optimal results, edit this document with help from docsify. To start:
- Run
npm i docsify-cli -g
- Clone the
styleguide
repository into your Workspace folder - Run
docsify init ./styleguide
, thendocsify serve styleguide
A web server will be spawned on port 3000. As you edit this README.md it will automatically refresh on the front-end.
To quote PEP 8,
A style guide is about consistency. Consistency with this style guide is important. Consistency within a project is more important. Consistency within one module or function is the most important.
Because our rules evolved over time, many older projects will not conform to them. If you find yourself revisiting an older project, it is more important to stay consistent with the rest of that project than it is to enforce these rules.
There is one exception to this: Do not escalate CSS nesting specificity wars. This harms maintainability in the future. If you possibly can, create a new, non-conflicting class for any new components.
Your code may be picked up by another developer at any time, or you might be called upon to work on a project on which you worked many months ago. Consequently, legibility is important; it reduces the overhead of getting involved (or re-involved) with a project.
If there is a choice between a clever and concise way of writing something, and a less clever and more verbose way of writing something to the same effect, you should tend towards the latter.
Comment anything that someone fresh to the project might not understand immediately. Don't write comments for the sake of writing comments. There is no need for this:
# Initialise the counter variable
counter = 0
# Loop over the items.
for item in some_list:
# Add 1 to counter
counter = counter + 1
The purpose of comments is not to describe what code does. They are to help other developers understand your code.
Anything particularly clever should have be commented (though you may want to see if you can write it in a less clever way). "Magic" numbers and behaviour should have a comment. Anything done to work around strange bugs should be commented. Anything which would cause another developer to say "why did they do this instead of X" (for values of X being some way that you would have tried or did try) should be commented.
If you know your code is going to be tricky, the rubber duck effect suggests that you should write your comments first - the act of describing a problem often makes it easier to solve!
Keystrokes are abundant. While not enforced, your variable names should be at least a single English (or technical) word. A positive Python example of what you should do:
class ThingyListView(ListView):
def get_queryset(self):
queryset = super().get_queryset()
queryset = queryset.filter(page__page=self.request.pages.current)
return queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['categories'] = Category.objects.all()
return context
It would be tempting to shorten context
and queryset
to ctx
and qs
. This would make your code harder to reason about.
Variable names should be in accord with the most common community-accepted standard for whatever language you are using. For example, we use function_names_with_underscores in Python, because this is what the Python community has largely agreed to use. We use camelCase in JavaScript, because this is what the language's standard library and the browser DOM use.
Most of these rules are enforced by Stylelint, both in pre-commit hooks and in CI.
- Indent blocks with two spaces
- A space before the opening curly brace ({)
- Properties and values on the same line
- Always have a space after the colon (:)
- Opening brace on the same line as our last selector ({);
- Declarations on a new line including after the first opening curly brace ({);
- Closing curly brace on a new line (});
- Trailing semicolon at the end of each declaration (;).
- Always end a CSS file with an empty line.
- Properties should always be lowercase.
- Shorten colour values - use #fff rather than #ffffff, for example.
We largely follow the Enduring CSS convention, which is very similar in philosophy (differing mainly in syntactical details) to the Block, Element, Modifier convention. Class names will take one of three forms.
namespace-Block
namespace-Block-modifier
namespace-Block_Element
namespace-Block_Element-modifier
Here, namespace
is a two-to-four letter namespace. This could be a distinct set of types of different components, or it could be For example, for things related to a site's header, this might be namespaced under hd-
. Different kinds of 'cards' would be namespaced crd-
. Different kinds of modular sections would be sec-
.
A block
is a distinctive component with standalone meaning. Its first letter should always be capitalised. If you are struggling to find a name for it that doesn't fit in a single word, use CondensedTitleCase
.
An element
is something which only ever exists within the context of a block
.
A modifier
describes a minor variation of a particular block or element.
An extremely simple example follows. Here is our HTML structure:
.sec-Section {
/* This is a standard section on a page, in cases where most or all sections
on a page have similar layout and spacing rules. The `.sec` namespace is
short for "sections", for pages that are built out of distinct, logically
separate sections. */
padding-top: 4vr;
padding-bottom: @padding-top;
background-color: #fff;
}
.sec-Section-grey {
/* This follows all the same rules as `.sec-Section` above, with one tiny
modification: its background colour is grey. */
background-color: var(--Color_Grey-light);
}
.sec-Section_Inner {
/* This is the inner part of a section. This doesn't have any applicability
outside of a section. (Of course, containers are used elsewhere on a site,
but we do not use utility classes.) */
@include Grid_Container;
}
.sec-Section_Header {
/* This defines the header for a section, with rules for how it will be
spaced from the content. */
margin-bottom: 2vr;
text-align: center;
}
.sec-Section_Title {
/* A section title which lives inside the above component. Its role is
logically distinct, and separable, from being part of the section header -
we define centre-aligned text and _Header above, not here. All this does is
describe a standard font that will be used on all (or most) section titles.
*/
@include Font_24-32;
}
.sec-Section_Body {
/* This defines the 'body' of a section. Note that we don't actually have
any assumptions about what that content might be! It might be a WYSIWYG
block, or it might be a list of things, or it might be a picture, - don't
make assumptions! More than likely whatever sits inside .sec-Section_Content
will be namespaced differently. For example, if it was a list of news
articles, we'd probably want to have something in the .lst- namespace. A
list of news articles is a list of news articles that could be
used in all sorts of ways, not just in something built with the section
builder. */
}
Do not have duplicate selectors. You should only need to look in one block to see all the properties and states of any given component. It is okay for a single block to have more than one selector, even when that selector has its own dedicated block elsewhere.
If your block has more than one selector, you should place each selector on a new line:
.frm-Form_Input,
.frm-Form_Textarea {
@include Font_14-20;
width: 100%;
border: 1px solid var(--Color_Border);
border-radius: 2px;
}
As much as possible, you should always target a single class name, and as much as possible this should not be dependent on its context.
Modifier classes should not be nested if they target the same element. Prefer this:
.crd-Card {
background-color: #fff;
}
.crd-Card-transparent {
background-color: transparent;
}
Rather than this:
.crd-Card {
background-color: #fff;
&.crd-Card-transparent {
background-color: transparent;
}
}
This keeps the specificity to a single class name.
If you need to modify the attributes of a card based on the type or state of one of its ancestors, you should usually nest the parent selector inside the child element's definition block, rather than nesting the child element in the parent's definition block.
.crd-Card_Body {
color: var(--Color_Body);
.crd-Card:hover & {
color: var(--Color_Blue);
}
}
In this case, it means you only need to look at one CSS block to see all the possible states for .crd-Card
.
Generally, you should avoid +
to target adjacent elements, when those adjacent elements use the same class name, and the parent element only contains (or can be made to only contain) elements of that class name selector. Take this example:
<ul class="lst-Stacked">
<li class="lst-Stacked_Item">...</li>
<li class="lst-Stacked_Item">...</li>
<li class="lst-Stacked_Item">...</li>
</ul>
The correct way to target adjacent elements (in this example, to put a margin between them) would be this:
.lst-Stacked {
margin-top: 1vr;
&:first-child {
margin-top: 0;
}
}
Instead of this:
.lst-Stacked {
& + & {
margin-top: 1vr;
}
}
Avoid !important
. There are rare exceptions, such as overriding other !important
declarations added by third-party code, or inline styles added by the same.
Don't target IDs in the form #selector
, as it has infinitely more specificity than class names.
If you are forced to target an ID (such as with elements injected by third-party code), target *[id="foo"]
instead.
Properties are grouped for readability, with an empty line separating adjacent groups, in this order:
.nsp-Component_ChildNode {
@include and @apply declarations;
content properties;
position properties;
flex properties;
display, width, height, margin and padding properties;
font and text properties;
background border and color properties;
animation and transition properties;
}
For further details, see the grouping configuration in the project template.
With the increasing complexity of Onespacemedia projects it is increasingly important to comment your CSS. Trying to understand your own code is difficult enough. Someone inheriting the project will find it more difficult than that.
There should always be a heading comment at the top of a .css file that includes the component name and the namespace selector:
/*
|--------------------------------------------------------------------------
| Component
|--------------------------------------------------------------------------
| @namespace: nsp-
|
*/
Then comments after should be grouped and order in the same way the HTML is ordered, keeping everything together in small sections in the file:
/*
|--------------------------------------------------------------------------
| Header
|--------------------------------------------------------------------------
*/
/*
|--------------------------------------------------------------------------
| Body
|--------------------------------------------------------------------------
*/
/*
|--------------------------------------------------------------------------
| Footer
|--------------------------------------------------------------------------
*/
If you use "magic" numbers in your CSS, add a comment above it explaining why you are using those numbers.
Comment any strange or clever CSS. Sometimes cross-browser fixes will require unusual rules that would not make any sense to a first-time observer, who might see it as a target for refactoring.
If you must add a /* stylelint-disable */
comment to your CSS to bypass linting, it is often good form to write an explanatory comment about why it is disabled immediately above the line, to avoid the tendency to /* stylelint-disable */
whenever a linting error is hit (for reasons both valid and invalid).
We use postcss-lh to give us a custom CSS unit based on the root line height of the document (typically, 24px). We've renamed postcss-lh
's default lh
unit to vr
, for vertical rhythm.
You can and should use these units for spacing and sizing where appropriate. Always use whole numbers or halves (e.g. 0.5vr
. Where you can't round up to one of these without severe visual deviation from the design, don't use vr
. Assuming 1vr
is 24 pixels, 6 pixels should be expressed as 6px
, not 0.25vr
.
Avoid em
; there are only very rare cases where this unit is necessary.
You never need to use rem
- our build system translates px
into rem
units (via postcss-pxtorem), so just use px
instead.
Otherwise, you should feel free to use whatever other unit is necessary to get a pleasing result.
Grid gutters and other horizontal margins should be in px
.
Vertical margins and padding should usually be specified in vr
or px
.
For percentages, make liberal use of calc
, especially for things that follow a site's grid. To specify a width of 5 columns out of twelve, minus the grid gutter, use width: calc(5 / 12 * 100% - var(--Grid_Gutter))
. It is much easier to understand the intent in this example at a quick glance than width: calc(41.66% - var(--Grid_Gutter))
.
Generally, you don't need to add vendor prefixes to property names and values; Autoprefixer handles this for you in our build system.
It is occasionally necessary to use vendor prefixes to work around weird cross-browser bugs, and in the vanishingly small number of cases that Autoprefixer does not work. In both these cases, you should add a comment to explain why you are using vendor prefixes.
Do not start z-index
wars, and don't escalate them if you see them in existing projects.
It's tempting to add an extremely high z-index
for things that you think absolutely must be on top of all other elements of a site. This often manifests itself as z-index: 99999
. And then someone notices something that needs to be on top of that, and adds an extra digit to make it z-index: 999999
. Then later, on a retainer task a few months down the line, someone adds an extra digit to that...
Generally, if you find yourself using more than low-single-digit z-indices on anything other than a global component like a sticky header, you are in a z-index
war. If you are using numbers greater than 100, you are definitely in a z-index
war.
If you can, use DOM ordering and position: relative
with no z-index if something has to sit above another thing within a component; you will be fine if you place the component later in the DOM at the same or higher level.
If you can't, use the lowest possible z-index
you can. Within the same component, 1 is usually sufficient.
In these examples, class names are omitted for readability.
Tag names, attributes, and attribute values should be in lowercase where possible.
Attribute values should be quoted in "double quotes"
Where an element starts on a new line to a parent element, it should be indented by two spaces. Elements that are rendered as blocks (whether they are block-level elements in the specification or made to render that way in CSS) should usually start on a new line. Avoid doing this if it will cause an ungrammatical space in text, though.
For self-closing tags that do not have textual content, omit closing tags where the HTML5 specification permits it. Avoid the XML form. This is fine:
<link rel="stylesheet" href="/static/css/style.css">
In cases where the HTML5 specification permits omitting an end tag, but the element has textual content, you should not omit the end tag. <li>
and <p>
, for example, may have their end tags omitted in the specification, but it's easier to spot the scope of an element with the explicit closing tag in place:
<ul>
<li>List item!</li>
</ul>
<p>A paragraph of text!</p>
<div>While this div, being a block-level element, would implicitly have closed the above P element if its end tag was omitted, the scope of the above element is more obvious with the explicit closing tag.</div>
An empty line between two HTML elements at the same indentation level is optional. Use your judgement.
Use the short form for attributes like checked
on <input>
and selected
on <option>
:
<select>
<option selected value="1">I'm the default option</option>
<option value="2">I'm not</option>
</select>
It is rarely useful for comments to be in the rendered HTML; you should usually use template-syntax {# comments #}
, rather than <!-- comments -->
. Similarly, for inline JavaScript, use template syntax instead of JavaScript // comments
or /* comments */
.
If Jinja/Django template comments in inline JS confuse your syntax highlighter, you can do something like this:
/* {# This comment is hidden from the rendered source, and also won't confuse syntax highlighters! #} */
Always put a space between the delimiters of a 'print' or of a template tag, and its name. This makes it much more readable:
{# Prints should have spaces inside the curlies. #}
<h1>Hello, {{ name }}!</h1>
<ul>
{# Tags and statements should have a space before and after the percent. #}
{% for animal in animals %}
<li>{{ animal }}</li>
{% endfor %}
</ul>
Indentation should be with two spaces.
You should not have more than one consecutive empty line.
A statement block, which is to say any tag that has a corresponding end
tag, should have its contents indented and on a new line if it contains any substantial amount of content.
It's fine to use a one liner to override a short string, as we quite often want to do with {% block %}
:
{% extends 'base.html' %}
{% block title %}I'm a mere short title{% endblock %}
{% block body_class %}body-IAmAcceptableToo{% endblock %}
{% block content %}
<p>As I contain a block-level HTML element and more than a trivial quantity of text, I start a new line and a new indentation level.</p>
{% endblock %}
There are times you should feel free to not do this to avoid unnecessary whitespace. For example, we can't put the if
on its own line here because it would cause an ungrammatical space before the comma:
Filed under {% for category in categories %}{{ category }}{% if not forloop.last %}, {% endif %}{% endfor %}
Block-level tags, which is to say those with a corresponding end
tag, should usually have an empty line between them and any other items at the same indentation level.
{% block content %}
<h1>{{ person.name }}</h1>
{% if person.nickname %}
<p>{{ person.nickname }}</p>
{% endif %}
{% with person.pet_set.all() as animals %}
{% if animals %} {# No empty line as it starts a new indentation level. #}
<h2>Pets</h2>
<ul>
{% for animal in animals %}
<li>{{ animal }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
{% endblock %}
Blocks should not be padded with an empty line. Do this:
{% if some_condition %}
<p>This is good!</p>
{% endif %}
Rather than this:
{% if some_condition %}
<p>Don't do this!</p>
{% endif %}
You should usually prefer {% with %}
, because it avoids bugs caused by scope pollution.
It's okay to use {% set %}
near the top of a file to create global shortcuts, such as this common case:
{% set content = pages.current.content %}
{% set %}
and {% with %}
assignments should have a space before and after the equals, just as one would in the generally-accepted Python style:
{% with articles = get_news_articles() %}
...
{% endwith %}
As well as being consistent with Python assignments, it means that constructs like this don't look weird:
{% with value1, value = get_some_tuple() %}
...
{% endwith %}
But as with generally accepted Python practice, keyword arguments passed to a function should not:
{% with articles = get_news_articles(count=5) %}
...
{% endwith %}
Function parameters should have a space after each comma, but never before:
{% with articles = get_news_articles(featured=True, count=5) %}
...
{% endwith %}
If your macro takes many arguments, consider whether you can consolidate content-related ones into a single argument.
An example might be a macro called like this, to render a card for a news article:
{{ cards.card(
article.title,
article.summary,
article.image,
article.get_absolute_url(),
modifier_parameter=True
) }}
In this case, consider whether you could build an as_card
method on your article model...
def as_card(self):
return {
'title': self.title,
'summary': self.summary,
'image': self.image,
'url': self.get_absolute_url(),
}
...and modify your card
macro to take a single first parameter for the card's content:
{{ cards.card(article.as_card(), modifier_parameter) }}
This has a bonus of moving excessive logic out of templates into Python, where anything but the most simple logic is much cleaner.
Where possible, SVGs should be inline in HTML rather than included with <img>
or applied with a background-image
. This permits targeting SVG paths in CSS for things like hover effects and animations.
For frequently-used icons, make liberal use of spriting to reduce the size of the document. Examples of sprite usage can be found in our project template.
With a few rare exceptions, SVGs should contain only path
elements, and you should be able to target the visible parts of the path with fill
alone (rather than strokes). This gives a single target for CSS, and a single CSS property for styling it. Ask a designer to convert the SVG for you if you don't know how to convert it yourself.
When inlining SVGs, be careful with SVGs exported by Adobe Illustrator and Sketch. Apart from being vastly bigger than necessary, they almost invariably cause SVGs with identical IDs, identical class names, and inline <style>
blocks. This often causes an SVG to apply its styles to paths in a different SVG earlier in the document.
SVGOMG is a useful tool for cleaning up SVG files; once you've worked out a good set of options you will find it does the right thing the vast majority of the time.
- Remove the
<?xml
declaration. - Remove all attributes from the root
svg
element exceptviewBox
. - Remove the entire
<defs>
block if one is present. - Remove any
id
attributes. - Remove any
g
groups. - Remove any explicit
fill=
attributes on paths. - Replace class names with ones conforming to our naming conventions (or for SVGs with a single colour, like most icons, remove class names altogether)
- Use CSS (within the standard frontend build system, not in the SVG itself) to style the SVGs, if necessary.
We conform to the Standard Style. In all semi-recent projects, this is enforced by the build system. To condense the Standard authors' summary:
- Two space for indentation
- Single quotes for strings, except to avoid escaping
- No unused variables
- No semicolons for statement termination
- Put a space after keywords and function names
- Use
===
instead of==
- Always prefix browser globals with
window
, exceptdocument
andnavigator
The one exception we've made to Standard's rules is that unused variables are a warning during development, not an error - unused variables should not make their way into production code.
Read the complete set of rules on the Standard site for more.
These rules apply for scripts processed by our frontend build system, which are transpiled to universally-browser-friendly ES5. In the rare cases where inline JavaScript is used in a document, you should avoid ES6 features, as they are not universally and consistently implemented in all the browsers we support.
Always use let
or const
, never var
. Always use const
for locals which are not reassigned.
Use ES6 fat-arrow functions for anonymous functions whenever possible, which is almost always.
window.addEventListener('DOMContentLoaded', () => {
doDomContentLoadedStuff()
})
Use object-literal shorthand where you can:
// In cases where our variable (or constant) names are the same as the names
// of our object keys, we can do this:
const animals = {
cow,
dog
}
Use ES6 string templates instead of ES5 concatenation:
const animal = 'cat'
const animalPlural = `${animal}s`
Use ES6 for...of
loops, rather than ES5 for
when traversing arrays:
for (const animal of animals) {
animal.makeNoise()
}
If in doubt, follow PEP 8.
Prefer 'single quotes' over "double quotes".
If a Python file consistently uses double quotes, then stay consistent with that file by using double quotes for new code within it.
If a file uses a mixture of single and double quotes, use single quotes for new code, but feel free to convert it to use single quotes consistently.
For quoting text that contains an apostrophe, use double quotes. This example would permit grepping for the string "don't":
have_bugs = models.BooleanField(
default=False,
help_text="Don't check this!"
)
Try to keep lines to a maximum of 79 characters.
This is a suggestion, not a rule. If limiting the line length would result in uglier code, then ignore it.
The last closing parenthesis, bracket, or brace should line up with the indentation level of the first line of the construct. For example, this is good:
ANIMAL_CHOICES = [
(1, 'Cat'),
(2, 'Dog'),
(3, 'Horse'),
]
Don't do this:
ANIMAL_CHOICES = [
(1, 'Cat'),
(2, 'Dog'),
(3, 'Horse'),
]
Multi-line array, dictionary, and tuples should have a dangling comma on the last item. Multi-line function calls may optionally have them too, though this isn't always possible (it's a syntax error in Python 2.x if your last argument is of the form **kwargs
).
For function calls (including class instantiation), if your declaration goes across more than one line, then the first argument should be on a new line:
objects = queryset.filter(
is_online=True,
page=self.request.pages.current,
)
Use isort's default ordering. Group imports thusly:
- Imports from
__future__
- Python standard library
- Third-party 'globally' installed packages (in our case, always installed in a virtual environment)
- Current project
- Explicit local imports (relative imports)
Use isort's "Grid" option for multi-line outputs.
Project-local imports should usually be relative imports. Imports from further up the directory structure should come first.
from ...utils.views import FilteredListView
from ..other_app.models import Taxonomy
from .models import Article, Category
For Python 3.6 projects (all new projects, and all projects since late 2017), you should use f-strings to format values into a string.
class Animal(models.Model):
# ....
def __str__(self):
return f'{self.name}, the {self.species}'
As well as being more concise, bad string formats are a compile-time error, so they will be caught as soon as your program starts. .format
and the %
operator will raise an exception at runtime, where they might go unnoticed for a while.
This is the general form of model fields:
class StyleGuide(models.Model):
title = models.CharField(
max_length=100,
blank=True,
null=True,
)
There should be a new line before the first argument to a field's constructor, and each argument should be on its own line. This makes diffs easier to read when arguments are added or deleted.
There should be a dangling comma after the last argument. Again, this makes diffs easier to read (because adding an argument to the end does not affect neighbouring lines).
String literals that are displayed to users, such as the help_text
for a model field, should not be broken across lines, regardless of their length. Don't do something like this:
title = models.CharField(
max_length=100,
help_text=(
'This is helpful text, but there is a problem because it spans more '
'than one line'
),
)
This makes it harder to search for distinctive strings; a grep
for "more than one line" will not return the file in which the above field lives. Do this instead:
title = models.CharField(
max_length=100,
help_text='This is helpful text, and because it is all on one line you can grep for any part of this string.',
)
However, it is rare to grep
across sentence boundaries. So if you need to avoid very large line lengths, this is fine:
title = models.CharField(
max_length=100,
help_text=(
'This is a multi-sentence help text that we are breaking across multiple lines. '
'It is unlikely that someone will be searching for, in this case, "multiple lines. It is". '
'So this does not harm greppability in practice.'
)
)
All models must define a __str__
method in Python 3 projects, or __unicode__
for Python 2.
All models should define an ordering
attribute in their Meta
class. Postgresql, our preferred database, will return rows in an "unspecified" order if you do not. Don't rely on primary key ordering to give date-added-based ordering; this is not guaranteed. Implement a "date added" field and have explicit ordering on that column.
Don't use a very large max_length
for CharField
. Generally, unless there are good technical reasons to do otherwise, don't supply a max_length
of more than 300. If you need more characters than that, use a TextField
.
A ForeignKey
, ManyToManyField
, etc, should usually use the string form for specifying the model:
page = models.ForeignKey(
'pages.Page',
)
articles = models.ManyToManyField(
'news.Article',
)
This avoids problems with recursive imports across apps.
Order model attributes thusly:
- Static class variables other than database fields (e.g., the common
urlconf = 'projectname.apps.app_name.urls'
for CMSContentBase
derivatives) - Database fields
- The
Meta
class - Double-underscore-prefixed special methods, e.g.
__unicode__
/__str__
- Overrides of standard model methods (e.g.
save()
), in alphabetical order - All other model methods, in alphabetical order
Always ensure that the plural form of a model name makes sense. By default, Django's admin will pluralise 'Category' as 'Categorys', for example. Use verbose_name_plural
to correct this:
class Category(models.Model):
title = models.CharField(
max_length=100,
)
class Meta:
verbose_name_plural = 'categories'
Where appropriate, supply an appropriate verbose_name
to ensure that capitalisation is correct for both model names and field names, including appropriate capitalisation for brand names. A model called Faq
should have a verbose_name
of 'FAQ'. A 'linkedin_url' field should have a verbose_name
of 'LinkedIn URL', to avoid the default admin rendering of 'Linkedin url'.
Similarly, where required, ensure that app names have an appropriate AppConfig
to ensure that app names render grammatically in the admin.
Always supply a help_text
for a field if you think it will be useful. Don't supply a help_text
if the field name is entirely self-explanatory.
Help text should be concise, grammatically-correct full sentences with punctuation. Avoid fluffy wording, poor spelling & grammar, and sentence fragments.
label = models.CharField(
max_length=100,
# Good example of a help_text.
help_text="This is never shown on the front end of the site; it's to help you identify this component in the admin.",
# Don't use sentence fragments like this:
# help_text="Identifies this component in the admin.",
# Or this, which has fluffy wording and adds no more information than the field name:
# help_text="Please enter a label for this component"
)
Use sentence case for capitalisation of app names, field names, fieldsets, and help texts.
Avoid Title Case.
A model's verbose_name
should usually be in lower case.
class VideoCategory(models.Model):
title = models.CharField(
max_length=100,
)
class Meta:
verbose_name = 'category'
verbose_name_plural = 'categories'
The Django admin will automatically capitalise the first letter of the verbose_name
where appropriate. Providing a verbose_name
of "Category" in this example would cause the name to be capitalised when its name is used mid-sentence, which is incorrect as 'Category' is not a proper noun.
In the case below, capitalising the 'T' in 'Twitter' is appropriate as it is a proper noun:
class TwitterAccount(models.Model):
username = models.CharField(
max_length=100,
)
class Meta:
verbose_name = 'Twitter account'
Always use class-based views. We have a nice tutorial.
View classes should have their attributes in this order:
- Static class attributes
- Double-underscore methods (
__init__
and friends) - Methods inherited from Django generic views, in alphabetical order
- Custom methods, in alphabetical order
Code commits should be as small as reasonably possible, and should contain specific changes described accurately in the commit message. Do not use generic commit messages such as "Made changes" or "Text tweak". Be descriptive!
Some tools will allow you to make changes without a commit message. Never do this.
If you have made multiple changes use the interactive commit system (git commit -p
) and cherry-pick the feature-specific code. Do not group multiple changes together in the same commit.
Squashing & merging is good when it does not remove any interesting history. Don't squash & merge if your branch history is long - it is likely that the history would be useful to other developers.
It's fine to squash commits if, for example, you made one commit and then a few others to fix linting (as often happens on our project template), because this history is not very important. On the other hand, if someone wants to look at the version history to understand how or why a certain bit of code was added, they may be upset to find it's a 10,000-line squashed commit with, say, the message "Write CRM system".
There is almost never a good reason for force-pushing (with the -f
option on command-line git
).
You should normally avoid --no-verify
to bypass pre-commit and pre-push hooks. Perhaps every developer knows the moment their one-line change that they were sure could not possibly break the build did, in fact, break the build.