Skip to content

Commit 98ea0ff

Browse files
committed
Added ruff-format pre-commit hook
Pretty much all changes in the `.py` files were made by Ruff, but a few of them were made manually by me to made some wrapped lines more readable. Also removed a few now-redundant entries in `CONTRIBUTING.md`.
1 parent 1f8b69e commit 98ea0ff

File tree

360 files changed

+14084
-6847
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

360 files changed

+14084
-6847
lines changed

.pre-commit-config.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
---
22
repos:
3+
- repo: https://github.com/astral-sh/ruff-pre-commit
4+
rev: v0.14.1
5+
hooks:
6+
- id: ruff-format
7+
38
- repo: https://github.com/pre-commit/pre-commit-hooks
49
rev: v6.0.0
510
hooks:

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ Lastly, a new [release](https://github.com/MAKENTNU/web/releases) must be create
4646
- Several of the standard `pre-commit-hooks` and `meta` hooks
4747
- `validate-pyproject`
4848
- `yamllint`
49+
- `ruff-format`
50+
- Formatted all Python code with Ruff (MAKENTNU/web#778)
4951

5052

5153
## 2025-05-03 (MAKENTNU/web#757)

CONTRIBUTING.md

Lines changed: 42 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,10 @@
2121
+ [Commit message](#commit-message)
2222
* [Python](#python)
2323
+ [For code in general](#for-code-in-general)
24-
- [PEP8](#pep8)
25-
- [String quotation marks](#string-quotation-marks)
2624
- [Quotation marks inside strings](#quotation-marks-inside-strings)
2725
- [String concatenation](#string-concatenation)
28-
- [Trailing commas](#trailing-commas)
29-
- [Operator wrapping](#operator-wrapping)
3026
- [Imports](#imports)
3127
+ [For each module (file)](#for-each-module-file)
32-
- [Empty/blank lines](#emptyblank-lines)
3328
- [Folder/directory location](#folderdirectory-location)
3429
- [Filename](#filename)
3530
+ [Migration filename](#migration-filename)
@@ -55,10 +50,10 @@
5550
+ [Model field argument value](#model-field-argument-value)
5651
* [Django templates / HTML / CSS](#django-templates--html--css)
5752
+ [For code in general](#for-code-in-general-1)
58-
- [String quotation marks](#string-quotation-marks-1)
53+
- [String quotation marks](#string-quotation-marks)
5954
- [Hex (color) code literals](#hex-color-code-literals)
6055
+ [For each file](#for-each-file)
61-
- [Empty/blank lines](#emptyblank-lines-1)
56+
- [Empty/blank lines](#emptyblank-lines)
6257
- [Filename](#filename-1)
6358
+ [Django template filenames](#django-template-filenames)
6459
+ [CSS filenames](#css-filenames)
@@ -78,7 +73,7 @@
7873
- [Stylesheet rule order](#stylesheet-rule-order)
7974
* [JavaScript](#javascript)
8075
+ [For code in general](#for-code-in-general-2)
81-
- [String quotation marks](#string-quotation-marks-2)
76+
- [String quotation marks](#string-quotation-marks-1)
8277
+ [For each file](#for-each-file-1)
8378
- [Filename](#filename-2)
8479
+ [For each function](#for-each-function)
@@ -152,37 +147,6 @@ with reasoning, examples and tips:
152147

153148
### For code in general
154149

155-
#### PEP8
156-
157-
In general, we follow the [PEP 8](https://www.python.org/dev/peps/pep-0008/) style guide.
158-
<br/>*Tip: PyCharm marks (most) PEP 8 violations with a yellow squiggly line.
159-
Additionally, PyCharm can easily fix most of these inconsistencies - and enforce various other parts of this style guide -
160-
using the [Reformat Code feature](https://www.jetbrains.com/pycharm/guide/tips/reformat-code/) (<kbd>Ctrl+Alt+L</kbd>/<kbd>⌥⌘L</kbd> by default) -
161-
possibly requiring some tweaking of the settings.*
162-
163-
#### String quotation marks
164-
165-
Use `'` for "code values" that are meant to be compared exactly against some other values.
166-
**In short: if these strings are changed, things *might* break.**
167-
Examples include:
168-
* Existing paths, like file paths for templates, or dotted module paths.
169-
* Names of variables or attributes, like object attributes, keyword arguments, or template context variables.
170-
171-
Use `"` for messages, names, etc. that are not supposed to be compared to existing values.
172-
**In short: if these strings are changed, nothing *should* break.**
173-
Examples include:
174-
* Text in a natural language, like messages that are shown to users or developers.
175-
* Path definitions - e.g. the first argument passed to the [`path()` function](https://docs.djangoproject.com/en/stable/ref/urls/#path) -
176-
as these are not supposed to be directly referenced elsewhere
177-
(they should instead be inserted using the `reverse()` function or the `url` template tag)
178-
and can in principle be changed freely without altering the functionality in any way.
179-
* Docstrings.
180-
181-
If unsure, or if the case in question is in a grey area, use `'`.
182-
183-
*[This specific coding style is loosely based on
184-
this library's coding standards](https://docs.ckan.org/en/ckan-2.7.3/contributing/python.html#use-single-quotes).*
185-
186150
#### Quotation marks inside strings
187151

188152
Prefer using the proper Unicode characters for quotation marks in the language of the string in question
@@ -199,41 +163,6 @@ if an f-string is hard to read, extract inserted code to variables, and insert t
199163
For translation strings (using `gettext` or `gettext_lazy`), use the standard `format()` method for concatenation.
200164
For example:<br/>`_("{chant} Batman!").format(chant="NaN" * 15)` (where `gettext_lazy` is imported as `_`).
201165

202-
#### Trailing commas
203-
204-
Always leave a trailing comma after the last element in a wrapped list/tuple/set/dictionary initialization expression
205-
or wrapped function/constructor call.
206-
207-
#### Operator wrapping
208-
209-
When wrapping an expression containing operators (like `+`, `&` and `and`),
210-
place the operators first on each wrapped line (instead of last on the previous lines).
211-
This also applies to similar things, like list comprehension expressions and conditional expressions (aka ternary operator).
212-
213-
To illustrate, a good example of wrapping the following code:
214-
```python
215-
def func(long_condition_expr, other_long_expr, list_of_lists):
216-
if long_condition_expr and long_condition_expr:
217-
return other_long_expr + other_long_expr
218-
return [element for inner_list in list_of_lists for element in inner_list if element is not None]
219-
```
220-
can be:
221-
```python
222-
def func(long_condition_expr, other_long_expr, list_of_lists):
223-
if (long_condition_expr
224-
and long_condition_expr):
225-
return (
226-
other_long_expr
227-
+ other_long_expr
228-
)
229-
return [
230-
element
231-
for inner_list in list_of_lists
232-
for element in inner_list
233-
if element is not None
234-
]
235-
```
236-
237166
#### Imports
238167

239168
Group imports in three "paragraphs" (separated by an empty line) in the following order:
@@ -253,10 +182,6 @@ All imports in a file that are from the same app as the mentioned file, should b
253182

254183
### For each module (file)
255184

256-
#### Empty/blank lines
257-
258-
Leave two empty lines between class and function (i.e. not method) definitions, and after all the imports in a module.
259-
260185
#### Folder/directory location
261186

262187
* Tests should always be placed within a `tests` directory per app.
@@ -470,12 +395,16 @@ from django.urls import path
470395

471396

472397
urlpatterns = [
473-
path("events/", ..., name='event_list'),
474-
path("events/add/", ..., name='event_create'),
475-
path("events/<int:pk>/", ..., name='event_detail'),
476-
path("events/<int:pk>/change/", ..., name='event_update'),
477-
path("events/<int:pk>/occurrences/", ..., name='event_occurrence_list'),
478-
path("events/<int:pk>/occurrences/<int:occurrence_pk>/", ..., name='event_occurrence_detail'),
398+
path("events/", ..., name="event_list"),
399+
path("events/add/", ..., name="event_create"),
400+
path("events/<int:pk>/", ..., name="event_detail"),
401+
path("events/<int:pk>/change/", ..., name="event_update"),
402+
path("events/<int:pk>/occurrences/", ..., name="event_occurrence_list"),
403+
path(
404+
"events/<int:pk>/occurrences/<int:occurrence_pk>/",
405+
...,
406+
name="event_occurrence_detail",
407+
),
479408
]
480409
```
481410
would be:
@@ -484,17 +413,17 @@ from django.urls import include, path
484413

485414

486415
event_occurrence_urlpatterns = [
487-
path("", ..., name='event_occurrence_list'),
488-
path("<int:pk>/", ..., name='event_occurrence_detail'),
416+
path("", ..., name="event_occurrence_list"),
417+
path("<int:pk>/", ..., name="event_occurrence_detail"),
489418
]
490419
specific_event_urlpatterns = [
491-
path("", ..., name='event_detail'),
492-
path("change/", ..., name='event_update'),
420+
path("", ..., name="event_detail"),
421+
path("change/", ..., name="event_update"),
493422
path("occurrences/", include(event_occurrence_urlpatterns)),
494423
]
495424
event_urlpatterns = [
496-
path("", ..., name='event_list'),
497-
path("add/", ..., name='event_create'),
425+
path("", ..., name="event_list"),
426+
path("add/", ..., name="event_create"),
498427
path("<int:pk>/", include(specific_event_urlpatterns)),
499428
]
500429

@@ -545,7 +474,7 @@ urlpatterns = [
545474
path("admin/", include(admin_urlpatterns)),
546475
path("api/", include(api_urlpatterns)),
547476
# The `news` app's base path route argument ("news/")
548-
path("news/", include('news.urls')),
477+
path("news/", include("news.urls")),
549478
]
550479
```
551480

@@ -558,7 +487,7 @@ Use `snake_case`.
558487

559488
An exception to this is when the variable value is a reference to a specific model class -
560489
in which case, the variable should have the same name as the model it references;
561-
for example: `InheritanceGroup = apps.get_model('groups', 'InheritanceGroup')`.
490+
for example: `InheritanceGroup = apps.get_model("groups", "InheritanceGroup")`.
562491

563492
#### Model field definition arguments
564493

@@ -601,7 +530,7 @@ Sort the keyword arguments in the following order:
601530

602531
Use `"` for everything pure HTML and CSS.
603532

604-
Inside template tags and filters, use [the same quotation marks as for Python](#string-quotation-marks).
533+
Inside template tags and filters, use the same quotation marks as for Python.
605534
Examples include:
606535
* `'` for:
607536
* `url` names
@@ -931,15 +860,15 @@ class EventOccurrence(models.Model):
931860
event = models.ForeignKey(
932861
to=Event,
933862
on_delete=models.CASCADE,
934-
related_name='occurrences',
863+
related_name="occurrences",
935864
)
936865

937866

938867
def get_context_data():
939868
# Prefetching related objects to avoid unnecessary additional database lookups
940869
return {
941-
'occurrence_objects': EventOccurrence.objects.select_related('event'),
942-
'event_objects': Event.objects.prefetch_related('occurrences'),
870+
"occurrence_objects": EventOccurrence.objects.select_related("event"),
871+
"event_objects": Event.objects.prefetch_related("occurrences"),
943872
}
944873
```
945874
```html
@@ -978,15 +907,15 @@ The number of database queries a request triggers, can be measured by adding the
978907
from web.settings import LOGGING
979908

980909

981-
LOGGING['loggers']['django.db.backends'] = {
982-
'level': 'DEBUG',
983-
'handlers': ['console'],
910+
LOGGING["loggers"]["django.db.backends"] = {
911+
"level": "DEBUG",
912+
"handlers": ["console"],
984913
}
985-
""" This would require a logging handler named `console`, which would look something like this:
986-
'console': {
987-
'level': 'DEBUG',
988-
'filters': [...],
989-
'class': 'logging.StreamHandler',
914+
""" This would require a logging handler named `console`, which could look like this:
915+
"console": {
916+
"level": "DEBUG",
917+
"filters": [...],
918+
"class": "logging.StreamHandler",
990919
}
991920
"""
992921
```
@@ -1153,18 +1082,20 @@ class EventOccurrenceForm(forms.Form):
11531082
def clean(self):
11541083
cleaned_data = super().clean()
11551084
# The fields won't exist in the cleaned data if they were submitted empty (even if they're both (by default) required)
1156-
start_time = cleaned_data.get('start_time')
1157-
end_time = cleaned_data.get('end_time')
1085+
start_time = cleaned_data.get("start_time")
1086+
end_time = cleaned_data.get("end_time")
11581087

11591088
# These might be `None`
11601089
if start_time and end_time:
11611090
if start_time > end_time:
11621091
error_message = _("The event cannot end before it starts.")
1163-
code = 'invalid_relative_to_other_field'
1164-
raise forms.ValidationError({
1165-
'start_time': forms.ValidationError(error_message, code=code),
1166-
'end_time': forms.ValidationError(error_message, code=code),
1167-
})
1092+
code = "invalid_relative_to_other_field"
1093+
raise forms.ValidationError(
1094+
{
1095+
"start_time": forms.ValidationError(error_message, code=code),
1096+
"end_time": forms.ValidationError(error_message, code=code),
1097+
}
1098+
)
11681099

11691100
return cleaned_data
11701101
```

env.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ def get_bool_envvar(name: str, *, default: bool = _NOT_PROVIDED) -> bool:
7070
"STATIC_AND_MEDIA_FILES__PARENT_DIR"
7171
)
7272
# The max size in prod is 50 MiB (through Nginx)
73-
MEDIA_FILE_MAX_SIZE__MB: Final = int(get_envvar("MEDIA_FILE_MAX_SIZE__MB", default="25"))
73+
MEDIA_FILE_MAX_SIZE__MB: Final = int(
74+
get_envvar("MEDIA_FILE_MAX_SIZE__MB", default="25")
75+
)
7476

7577
# --- `channels` ---
7678
REDIS_HOST: Final = get_envvar("REDIS_HOST", default="localhost")

manage.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
#!/usr/bin/env python
22
"""Django's command-line utility for administrative tasks."""
3+
34
import os
45
import sys
56

6-
sys.path.append('src')
7+
sys.path.append("src")
78

89

910
def main():
1011
"""Run administrative tasks."""
1112
# IMPORTANT: Ensure this import is kept here, as it loads the envvars
1213
import env # noqa: F401
1314

14-
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'web.settings')
15+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "web.settings")
1516
try:
1617
from django.core.management import execute_from_command_line
1718
except ImportError as exc:
@@ -23,5 +24,5 @@ def main():
2324
execute_from_command_line(sys.argv)
2425

2526

26-
if __name__ == '__main__':
27+
if __name__ == "__main__":
2728
main()

src/announcements/admin.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,36 @@
22
from django.template.defaultfilters import urlize
33
from django.utils.translation import gettext_lazy as _
44

5-
from util.admin_utils import DefaultAdminWidgetsMixin, list_filter_factory, search_escaped_and_unescaped
5+
from util.admin_utils import (
6+
DefaultAdminWidgetsMixin,
7+
list_filter_factory,
8+
search_escaped_and_unescaped,
9+
)
610
from .models import Announcement
711

812

913
class AnnouncementAdmin(DefaultAdminWidgetsMixin, admin.ModelAdmin):
10-
list_display = ('content', 'classification', 'site_wide', 'get_is_shown', 'get_link', 'display_from', 'display_to')
14+
list_display = (
15+
"content",
16+
"classification",
17+
"site_wide",
18+
"get_is_shown",
19+
"get_link",
20+
"display_from",
21+
"display_to",
22+
)
1123
list_filter = (
12-
'classification', 'site_wide',
24+
"classification",
25+
"site_wide",
1326
list_filter_factory(
14-
_("shown"), 'shown', lambda qs, yes_filter: qs.shown() if yes_filter else qs.not_shown(),
27+
_("shown"),
28+
"shown",
29+
lambda qs, yes_filter: qs.shown() if yes_filter else qs.not_shown(),
1530
),
1631
)
17-
search_fields = ('content', 'link')
18-
list_editable = ('classification', 'site_wide')
19-
ordering = ('-display_from',)
32+
search_fields = ("content", "link")
33+
list_editable = ("classification", "site_wide")
34+
ordering = ("-display_from",)
2035

2136
@admin.display(
2237
boolean=True,
@@ -26,7 +41,7 @@ def get_is_shown(self, announcement: Announcement):
2641
return announcement.is_shown()
2742

2843
@admin.display(
29-
ordering='link',
44+
ordering="link",
3045
description=_("link"),
3146
)
3247
def get_link(self, announcement: Announcement):

src/announcements/apps.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22

33

44
class AnnouncementsConfig(AppConfig):
5-
default_auto_field = 'django.db.models.BigAutoField'
6-
name = 'announcements'
5+
default_auto_field = "django.db.models.BigAutoField"
6+
name = "announcements"

0 commit comments

Comments
 (0)