diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml
index 56244ca..fcf6213 100644
--- a/.github/workflows/linting.yml
+++ b/.github/workflows/linting.yml
@@ -30,7 +30,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
- python: ['3.7', '3.8', '3.9', '3.10']
+ python: ['3.9', '3.10', '3.11']
fail-fast: false
steps:
diff --git a/Makefile b/Makefile
index 2238ba3..ad1c188 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-#
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
diff --git a/fabfile.py b/fabfile.py
index 3ddb927..1744a99 100644
--- a/fabfile.py
+++ b/fabfile.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
diff --git a/geany.org.service b/geany.org.service
index 9712e82..4fe841d 100644
--- a/geany.org.service
+++ b/geany.org.service
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
diff --git a/geany/apps.py b/geany/apps.py
index dc310e6..c5f2a48 100644
--- a/geany/apps.py
+++ b/geany/apps.py
@@ -1,4 +1,3 @@
-# coding: utf-8
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -17,4 +16,4 @@
class GeanyAppConfig(AppConfig):
name = 'geany'
- verbose_name = "Geany"
+ verbose_name = 'Geany'
diff --git a/geany/decorators.py b/geany/decorators.py
index 97da831..2643756 100644
--- a/geany/decorators.py
+++ b/geany/decorators.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# based on http://djangosnippets.org/snippets/564/
#
# LICENCE: This program is free software: you can redistribute it and/or modify
@@ -14,8 +13,8 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-from binascii import hexlify
import inspect
+from binascii import hexlify
from django.core.cache import cache as _djcache
diff --git a/geany/management/commands/dump_database.py b/geany/management/commands/dump_database.py
index 629c707..b97aba2 100644
--- a/geany/management/commands/dump_database.py
+++ b/geany/management/commands/dump_database.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -23,7 +22,7 @@
class Command(BaseCommand):
- help = "Dump the database (excluding users, sessions and logs)"
+ help = 'Dump the database (excluding users, sessions and logs)' # noqa: A003
# ----------------------------------------------------------------------
def handle(self, *args, **options):
@@ -33,7 +32,7 @@ def handle(self, *args, **options):
# ----------------------------------------------------------------------
def _dump_main_database(self):
- print('Dump main database')
+ self.stdout.write('Dump main database')
call_command(
'dumpdata',
'--exclude', 'auth.user',
@@ -97,7 +96,7 @@ def _dump_nightly_builds_database(self):
database_nightlybuilds_filename = 'tmp_database_nightlybuilds.json'
database_nightlybuild_targets_filename = 'tmp_database_nightlybuild_targets.json'
- print('Dump nightlybuilds.NightlyBuild')
+ self.stdout.write('Dump nightlybuilds.NightlyBuild')
call_command(
'dumpdata',
'--database', 'nightlybuilds',
@@ -106,7 +105,7 @@ def _dump_nightly_builds_database(self):
'--output', database_nightlybuilds_filename,
'nightlybuilds.NightlyBuild')
- print('Dump nightlybuilds.NightlyBuildTarget')
+ self.stdout.write('Dump nightlybuilds.NightlyBuildTarget')
call_command(
'dumpdata',
'--database', 'nightlybuilds',
diff --git a/geany/settings.py b/geany/settings.py
index 586a450..8c9f968 100644
--- a/geany/settings.py
+++ b/geany/settings.py
@@ -19,6 +19,8 @@
from markdown.extensions.toc import TocExtension
+# ruff: noqa: ERA001
+
######################
# MEZZANINE SETTINGS #
######################
@@ -109,7 +111,7 @@
# ('/admin', '/example') would force all URLs beginning with
# /admin or /example to run over SSL. Defaults to:
#
-SSL_FORCE_URL_PREFIXES = ("/admin", "/account")
+SSL_FORCE_URL_PREFIXES = ('/admin', '/account')
# Django security settings
SECURE_CONTENT_TYPE_NOSNIFF = True
@@ -138,7 +140,7 @@
'www.geany.org')
INTERNAL_IPS = ('127.0.0.1', '10.0.44.3')
-SECRET_KEY = 'change-me'
+SECRET_KEY = 'change-me' # noqa: S105
# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
@@ -154,7 +156,7 @@
# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
-LANGUAGE_CODE = "en"
+LANGUAGE_CODE = 'en'
# Supported languages
LANGUAGES = (
@@ -175,7 +177,7 @@
# to load the internationalization machinery.
USE_I18N = False
-AUTHENTICATION_BACKENDS = ("mezzanine.core.auth_backends.MezzanineBackend",)
+AUTHENTICATION_BACKENDS = ('mezzanine.core.auth_backends.MezzanineBackend',)
# The numeric mode to set newly-uploaded files to. The value should be
# a mode you'd pass directly to os.chmod.
@@ -193,24 +195,24 @@
#############
DATABASES = {
- "default": {
- "ENGINE": "django.db.backends.mysql",
- "NAME": "dbname",
- "USER": "",
- "PASSWORD": "",
- "HOST": "127.0.0.1",
- "OPTIONS": {
- "init_command": "SET sql_mode='STRICT_ALL_TABLES'; SET storage_engine=INNODB"
+ 'default': {
+ 'ENGINE': 'django.db.backends.mysql',
+ 'NAME': 'dbname',
+ 'USER': '',
+ 'PASSWORD': '',
+ 'HOST': '127.0.0.1',
+ 'OPTIONS': {
+ 'init_command': "SET sql_mode='STRICT_ALL_TABLES'; SET storage_engine=INNODB"
},
},
'nightlybuilds': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'dbname',
- "USER": "",
- "PASSWORD": "",
- "HOST": "127.0.0.1",
- "OPTIONS": {
- "init_command": "SET sql_mode='STRICT_ALL_TABLES';"
+ 'USER': '',
+ 'PASSWORD': '',
+ 'HOST': '127.0.0.1',
+ 'OPTIONS': {
+ 'init_command': "SET sql_mode='STRICT_ALL_TABLES';"
},
}
}
@@ -238,22 +240,22 @@
# URL prefix for static files.
# Example: "http://media.lawrence.com/static/"
-STATIC_URL = "/static/"
+STATIC_URL = '/static/'
# Absolute path to the directory static files should be collected to.
# Don't put anything in this directory yourself; store your static files
# in apps' "static/" subdirectories and in STATICFILES_DIRS.
# Example: "/home/media/media.lawrence.com/static/"
-STATIC_ROOT = os.path.join(PROJECT_ROOT, STATIC_URL.strip("/"))
+STATIC_ROOT = os.path.join(PROJECT_ROOT, STATIC_URL.strip('/'))
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash.
# Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
-MEDIA_URL = "/media/"
+MEDIA_URL = '/media/'
# Absolute filesystem path to the directory that will hold user-uploaded files.
# Example: "/home/media/media.lawrence.com/media/"
-MEDIA_ROOT = os.path.join(PROJECT_ROOT, *MEDIA_URL.strip("/").split("/"))
+MEDIA_ROOT = os.path.join(PROJECT_ROOT, *MEDIA_URL.strip('/').split('/'))
# Package/module name to import the root urlpatterns from for the project.
ROOT_URLCONF = f'{PROJECT_APP}.urls'
@@ -263,7 +265,7 @@
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'APP_DIRS': True,
'DIRS': [
- os.path.join(PROJECT_APP_PATH, "templates"),
+ os.path.join(PROJECT_APP_PATH, 'templates'),
],
'OPTIONS': {
'context_processors': [
@@ -286,53 +288,53 @@
# List of finder classes that know how to find static files in
# various locations.
STATICFILES_FINDERS = (
- "django.contrib.staticfiles.finders.FileSystemFinder",
- "django.contrib.staticfiles.finders.AppDirectoriesFinder",
+ 'django.contrib.staticfiles.finders.FileSystemFinder',
+ 'django.contrib.staticfiles.finders.AppDirectoriesFinder',
'compressor.finders.CompressorFinder',
)
# Store these package names here as they may change in the future since
# at the moment we are using custom forks of them.
-PACKAGE_NAME_FILEBROWSER = "filebrowser_safe"
-PACKAGE_NAME_GRAPPELLI = "grappelli_safe"
+PACKAGE_NAME_FILEBROWSER = 'filebrowser_safe'
+PACKAGE_NAME_GRAPPELLI = 'grappelli_safe'
################
# APPLICATIONS #
################
INSTALLED_APPS = (
- "clearcache",
- "django.contrib.admin",
- "django.contrib.auth",
- "django.contrib.contenttypes",
- "django.contrib.messages",
- "django.contrib.redirects",
- "django.contrib.sessions",
- "django.contrib.sites",
- "django.contrib.sitemaps",
- "django.contrib.staticfiles",
- "mezzanine.boot",
- "mezzanine.conf",
- "mezzanine.core",
- "mezzanine.generic",
- "mezzanine.pages",
- "mezzanine.forms",
- "mezzanine.galleries",
+ 'clearcache',
+ 'django.contrib.admin',
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.messages',
+ 'django.contrib.redirects',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'django.contrib.sitemaps',
+ 'django.contrib.staticfiles',
+ 'mezzanine.boot',
+ 'mezzanine.conf',
+ 'mezzanine.core',
+ 'mezzanine.generic',
+ 'mezzanine.pages',
+ 'mezzanine.forms',
+ 'mezzanine.galleries',
# we
- "geany.apps.GeanyAppConfig",
- "news.apps.NewsAppConfig",
- "latest_version.apps.LatestVersionAppConfig",
- "static_docs.apps.StaticDocsAppConfig",
- "pastebin.apps.PastebinAppConfig",
- "nightlybuilds.apps.NightlyBuildsAppConfig",
- "urlshortener.apps.UrlShortenerAppConfig",
+ 'geany.apps.GeanyAppConfig',
+ 'news.apps.NewsAppConfig',
+ 'latest_version.apps.LatestVersionAppConfig',
+ 'static_docs.apps.StaticDocsAppConfig',
+ 'pastebin.apps.PastebinAppConfig',
+ 'nightlybuilds.apps.NightlyBuildsAppConfig',
+ 'urlshortener.apps.UrlShortenerAppConfig',
# 3rd party
- "compressor",
- "django_extensions",
- "mezzanine_pagedown",
- "mezzanine_sync_pages.apps.MezzanineSyncPagesAppConfig",
+ 'compressor',
+ 'django_extensions',
+ 'mezzanine_pagedown',
+ 'mezzanine_sync_pages.apps.MezzanineSyncPagesAppConfig',
# "shortener", # disabled until it is fixed for Django 4.0 or we remove it completely
PACKAGE_NAME_FILEBROWSER,
PACKAGE_NAME_GRAPPELLI,
@@ -342,27 +344,27 @@
# these middleware classes will be applied in the order given, and in the
# response phase the middleware will be applied in reverse order.
MIDDLEWARE = (
- "log_request_id.middleware.RequestIDMiddleware",
- "mezzanine.core.middleware.UpdateCacheMiddleware",
+ 'log_request_id.middleware.RequestIDMiddleware',
+ 'mezzanine.core.middleware.UpdateCacheMiddleware',
- "django.middleware.security.SecurityMiddleware",
- "django.contrib.sessions.middleware.SessionMiddleware",
+ 'django.middleware.security.SecurityMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
# Uncomment if using internationalisation or localisation
# "django.middleware.locale.LocaleMiddleware",
- "django.middleware.common.CommonMiddleware",
- "django.middleware.csrf.CsrfViewMiddleware",
- "django.contrib.auth.middleware.AuthenticationMiddleware",
- "django.contrib.messages.middleware.MessageMiddleware",
- "django.middleware.clickjacking.XFrameOptionsMiddleware",
-
- "mezzanine.core.request.CurrentRequestMiddleware",
- "mezzanine.core.middleware.RedirectFallbackMiddleware",
- "mezzanine.core.middleware.AdminLoginInterfaceSelectorMiddleware",
- "mezzanine.core.middleware.SitePermissionMiddleware",
+ 'django.middleware.common.CommonMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+
+ 'mezzanine.core.request.CurrentRequestMiddleware',
+ 'mezzanine.core.middleware.RedirectFallbackMiddleware',
+ 'mezzanine.core.middleware.AdminLoginInterfaceSelectorMiddleware',
+ 'mezzanine.core.middleware.SitePermissionMiddleware',
# Uncomment the following if using any of the SSL settings:
# "mezzanine.core.middleware.SSLRedirectMiddleware",
- "mezzanine.pages.middleware.PageMiddleware",
- "mezzanine.core.middleware.FetchFromCacheMiddleware",
+ 'mezzanine.pages.middleware.PageMiddleware',
+ 'mezzanine.core.middleware.FetchFromCacheMiddleware',
)
# pagedown / markdown
@@ -396,33 +398,33 @@
# dashboard
DASHBOARD_TAGS = (
- ("mezzanine_tags.app_list",),
- ("mezzanine_tags.recent_actions",),
- ("comment_tags.recent_comments",),
+ ('mezzanine_tags.app_list',),
+ ('mezzanine_tags.recent_actions',),
+ ('comment_tags.recent_comments',),
)
ADMIN_MENU_ORDER = (
- (_("Content"), (
- "pages.Page",
- "news.NewsPost",
- "generic.ThreadedComment",
- "mezzanine_blocks.Block",
- "mezzanine_blocks.RichBlock",
- "mezzanine_sync_pages.MezzanineSyncPages",
- (_("Media Library"), "fb_browse"),)),
- (_("Site"), (
- "sites.Site",
- "redirects.Redirect",
- "conf.Setting",
- (_("Clear Cache"), "clearcache_admin"),
+ (_('Content'), (
+ 'pages.Page',
+ 'news.NewsPost',
+ 'generic.ThreadedComment',
+ 'mezzanine_blocks.Block',
+ 'mezzanine_blocks.RichBlock',
+ 'mezzanine_sync_pages.MezzanineSyncPages',
+ (_('Media Library'), 'fb_browse'),)),
+ (_('Site'), (
+ 'sites.Site',
+ 'redirects.Redirect',
+ 'conf.Setting',
+ (_('Clear Cache'), 'clearcache_admin'),
)),
- (_("Geany"), (
- "latest_version.LatestVersion",
+ (_('Geany'), (
+ 'latest_version.LatestVersion',
)),
- (_("Users"), ("auth.User", "auth.Group",))) # pylint: disable=hard-coded-auth-user
+ (_('Users'), ('auth.User', 'auth.Group',))) # pylint: disable=hard-coded-auth-user
# caching & sessions
-SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"
+SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
SESSION_COOKIE_SECURE = True
@@ -457,7 +459,7 @@ def skip_404_not_found(record):
return True
-logging.captureWarnings(True) # log warnings using the logging subsystem
+logging.captureWarnings(capture=True) # log warnings using the logging subsystem
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
@@ -602,14 +604,15 @@ def skip_404_not_found(record):
local_settings_file_name = os.environ.get('LOCAL_SETTINGS_PY', 'local_settings.py')
filename = os.path.join(PROJECT_APP_PATH, local_settings_file_name) # pylint: disable=invalid-name
if os.path.exists(filename):
- from importlib.util import module_from_spec, spec_from_file_location
import sys
+ from importlib.util import module_from_spec, spec_from_file_location
+
module_name = f'{PROJECT_APP}.local_settings' # pylint: disable=invalid-name
spec = spec_from_file_location(module_name, filename) # pylint: disable=invalid-name
module = module_from_spec(spec) # pylint: disable=invalid-name
sys.modules[module_name] = module
with open(filename, 'rb') as local_config:
- exec(local_config.read()) # pylint: disable=exec-used
+ exec(local_config.read()) # noqa: S102 pylint: disable=exec-used
####################
diff --git a/geany/sitemaps.py b/geany/sitemaps.py
index 8141ac8..d18c7e2 100644
--- a/geany/sitemaps.py
+++ b/geany/sitemaps.py
@@ -1,4 +1,3 @@
-# coding: utf-8
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -181,9 +180,9 @@ def _get_static_items(self):
def _get_items(self, item_generator_name):
items = []
for generator_class, url_patterns, site_domain, exclude_views in self._sitemap_generators:
- site_domain = self._get_site_domain_or_default(site_domain)
+ site_domain_final = self._get_site_domain_or_default(site_domain)
generator = generator_class(
- site_domain,
+ site_domain_final,
url_patterns,
exclude_views=exclude_views)
item_generator = getattr(generator, item_generator_name)
diff --git a/geany/templatetags/geany_tags.py b/geany/templatetags/geany_tags.py
index 8570633..71fc10c 100644
--- a/geany/templatetags/geany_tags.py
+++ b/geany/templatetags/geany_tags.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -53,8 +52,8 @@ def do_evaluate(parser, token): # pylint: disable=unused-argument
_, variable, _, target_var_name = token.split_contents()
except ValueError as exc:
token_name = token.contents.split()[1]
- raise template.TemplateSyntaxError(
- f'{token_name!r} tag requires a single argument') from exc
+ msg = f'{token_name!r} tag requires a single argument'
+ raise template.TemplateSyntaxError(msg) from exc
return EvaluateNode(variable, target_var_name)
diff --git a/geany/urls.py b/geany/urls.py
index 863677c..4dc9509 100644
--- a/geany/urls.py
+++ b/geany/urls.py
@@ -11,6 +11,7 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
+import mezzanine_pagedown.urls
from django.conf.urls.i18n import i18n_patterns
from django.contrib import admin
from django.contrib.sitemaps.views import sitemap
@@ -19,7 +20,6 @@
from django.views.i18n import set_language
from django.views.static import serve as static_serve
from mezzanine.conf import settings
-import mezzanine_pagedown.urls
from geany import urls_legacy
from geany.sitemaps import GeanyMainSitemap
@@ -28,7 +28,7 @@
# pylint: disable=invalid-name
-sitemaps = {"sitemaps": {"all": GeanyMainSitemap}}
+sitemaps = {'sitemaps': {'all': GeanyMainSitemap}}
admin.autodiscover()
@@ -37,7 +37,7 @@
# Change the admin prefix here to use an alternate URL for the
# admin interface, which would be marginally more secure.
path('admin/clearcache/', include('clearcache.urls')),
- path("admin/", include(admin.site.urls)),
+ path('admin/', include(admin.site.urls)),
)
if settings.USE_MODELTRANSLATION:
@@ -63,7 +63,8 @@
path('p/', include('pastebin.urls')),
# URL Shortener
- # path('s/', include('urlshortener.urls')), # disabled until it is fixed for Django 4.0
+ # disabled until it is fixed for Django 4.0
+ # path('s/', include('urlshortener.urls')), # noqa: ERA001
# /news/ News
path('news/', include('news.urls')),
diff --git a/geany/urls_legacy.py b/geany/urls_legacy.py
index 1e9cd23..fd133af 100644
--- a/geany/urls_legacy.py
+++ b/geany/urls_legacy.py
@@ -185,7 +185,7 @@
def _add_url_mappings(mapping, urlpatterns_):
for old_url, new_url in mapping.items():
if old_url.startswith('/'):
- old_url = old_url[1:]
+ old_url = old_url[1:] # noqa: PLW2901
url_pattern = re_path(
fr'^{old_url}$',
diff --git a/latest_version/admin.py b/latest_version/admin.py
index b501ea2..c7f8651 100644
--- a/latest_version/admin.py
+++ b/latest_version/admin.py
@@ -1,4 +1,3 @@
-# coding: utf-8
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
diff --git a/latest_version/apps.py b/latest_version/apps.py
index a1d2aa1..d19937d 100644
--- a/latest_version/apps.py
+++ b/latest_version/apps.py
@@ -1,4 +1,3 @@
-# coding: utf-8
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -17,12 +16,12 @@
class LatestVersionAppConfig(AppConfig):
name = 'latest_version'
- verbose_name = "LatestVersion"
+ verbose_name = 'LatestVersion'
# ----------------------------------------------------------------------
def ready(self):
# register our urlpatterns to the global sitemap generator
# pylint: disable=import-outside-toplevel
- from geany.sitemaps import sitemap_registry, StaticSitemap
+ from geany.sitemaps import StaticSitemap, sitemap_registry
from latest_version.urls import urlpatterns
sitemap_registry.add(StaticSitemap, urlpatterns, exclude_views=['latest_version_legacy'])
diff --git a/latest_version/context_processors.py b/latest_version/context_processors.py
index ba7b85d..b194173 100644
--- a/latest_version/context_processors.py
+++ b/latest_version/context_processors.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -15,9 +14,9 @@
from mezzanine.conf import settings
from geany.decorators import (
- cache_function,
CACHE_KEY_LATEST_VERSION_LATEST_VERSION,
CACHE_TIMEOUT_1HOUR,
+ cache_function,
)
from latest_version.models import LatestVersion
from latest_version.releases import ReleaseVersionsProvider
diff --git a/latest_version/models.py b/latest_version/models.py
index 17dcc15..287d1c5 100644
--- a/latest_version/models.py
+++ b/latest_version/models.py
@@ -1,4 +1,3 @@
-# coding: utf-8
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -36,8 +35,8 @@ class Meta:
verbose_name_plural = 'Latest Version'
# ----------------------------------------------------------------------
- def delete(self, using=None, keep_parents=False):
- """Never delete anything"""
+ def __str__(self):
+ return f'{self.name} {self.version}'
# ----------------------------------------------------------------------
def save(self, *args, **kwargs): # pylint: disable=signature-differs
@@ -47,5 +46,5 @@ def save(self, *args, **kwargs): # pylint: disable=signature-differs
[CACHE_KEY_LATEST_VERSION_LATEST_VERSION, CACHE_KEY_STATIC_DOCS_RELEASE_NOTES])
# ----------------------------------------------------------------------
- def __str__(self):
- return f'{self.name} {self.version}'
+ def delete(self, using=None, keep_parents=False):
+ """Never delete anything"""
diff --git a/latest_version/releases.py b/latest_version/releases.py
index c2b2122..44412db 100644
--- a/latest_version/releases.py
+++ b/latest_version/releases.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -12,9 +11,9 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-from pathlib import Path
import logging
import re
+from pathlib import Path
from django.conf import settings
from packaging.version import parse as parse_version
diff --git a/news/admin.py b/news/admin.py
index 6fc72d0..ab70bac 100644
--- a/news/admin.py
+++ b/news/admin.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -20,12 +19,12 @@
class NewsPostAdmin(admin.ModelAdmin):
list_display = ('title', 'user', 'status', 'publish_date')
- list_editable = ("status",)
+ list_editable = ('status',)
list_filter = ('publish_date', 'status')
date_hierarchy = 'publish_date'
exclude = ('slug', 'user', 'entry_date')
actions = ['_toggle_many_published']
- radio_fields = {"status": admin.HORIZONTAL}
+ radio_fields = {'status': admin.HORIZONTAL}
# ----------------------------------------------------------------------
def save_model(self, request, obj, form, change):
diff --git a/news/apps.py b/news/apps.py
index 211487a..e0301b8 100644
--- a/news/apps.py
+++ b/news/apps.py
@@ -1,4 +1,3 @@
-# coding: utf-8
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -17,7 +16,7 @@
class NewsAppConfig(AppConfig):
name = 'news'
- verbose_name = "News"
+ verbose_name = 'News'
# ----------------------------------------------------------------------
def ready(self):
diff --git a/news/feeds.py b/news/feeds.py
index 2e3635a..92d7fe0 100644
--- a/news/feeds.py
+++ b/news/feeds.py
@@ -1,4 +1,3 @@
-# coding: utf-8
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -23,12 +22,12 @@
class LatestNewsPostsFeed(Feed):
- title = "Geany project news"
- description = "News feed for the Geany project"
+ title = 'Geany project news'
+ description = 'News feed for the Geany project'
# ----------------------------------------------------------------------
def link(self):
- return reverse("news_list")
+ return reverse('news_list')
# ----------------------------------------------------------------------
def items(self):
@@ -41,7 +40,7 @@ def item_title(self, item):
# ----------------------------------------------------------------------
def item_description(self, item):
description = richtext_filters(item.content)
- absolute_urls_name = "mezzanine.utils.html.absolute_urls"
+ absolute_urls_name = 'mezzanine.utils.html.absolute_urls'
if absolute_urls_name not in settings.RICHTEXT_FILTERS:
description = absolute_urls(description)
return description
diff --git a/news/models.py b/news/models.py
index ebd715c..a2751c4 100644
--- a/news/models.py
+++ b/news/models.py
@@ -83,6 +83,10 @@ class Meta:
verbose_name = _('News')
verbose_name_plural = _('News')
+ # ----------------------------------------------------------------------
+ def __str__(self):
+ return self.title
+
# ----------------------------------------------------------------------
def save(self, *args, **kwargs): # pylint: disable=signature-differs
if not self.slug:
@@ -92,7 +96,3 @@ def save(self, *args, **kwargs): # pylint: disable=signature-differs
# ----------------------------------------------------------------------
def get_absolute_url(self):
return reverse('news_detail', kwargs={'newspost_slug': self.slug})
-
- # ----------------------------------------------------------------------
- def __str__(self):
- return self.title
diff --git a/news/sitemaps.py b/news/sitemaps.py
index b08b556..e66ebfd 100644
--- a/news/sitemaps.py
+++ b/news/sitemaps.py
@@ -1,4 +1,3 @@
-# coding: utf-8
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
diff --git a/news/templatetags/news_tags.py b/news/templatetags/news_tags.py
index eedee03..6f40cb4 100644
--- a/news/templatetags/news_tags.py
+++ b/news/templatetags/news_tags.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -21,8 +20,8 @@
# ----------------------------------------------------------------------
-@register.inclusion_tag("news/list_embedded.html", takes_context=True)
+@register.inclusion_tag('news/list_embedded.html', takes_context=True)
def get_recent_news(context):
user = context.request.user
- context["recent_news_posts"] = NewsPost.objects.recently_published(count=4, for_user=user)
+ context['recent_news_posts'] = NewsPost.objects.recently_published(count=4, for_user=user)
return context
diff --git a/news/views.py b/news/views.py
index 402ec5d..26a87e7 100644
--- a/news/views.py
+++ b/news/views.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
diff --git a/nightlybuilds/apps.py b/nightlybuilds/apps.py
index c0c8198..5f8eb63 100644
--- a/nightlybuilds/apps.py
+++ b/nightlybuilds/apps.py
@@ -1,4 +1,3 @@
-# coding: utf-8
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -17,4 +16,4 @@
class NightlyBuildsAppConfig(AppConfig):
name = 'nightlybuilds'
- verbose_name = "Nightly Builds"
+ verbose_name = 'Nightly Builds'
diff --git a/nightlybuilds/database_routers.py b/nightlybuilds/database_routers.py
index 23ee0c5..031b6f5 100644
--- a/nightlybuilds/database_routers.py
+++ b/nightlybuilds/database_routers.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -12,6 +11,7 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
+# ruff: noqa: SLF001
# pylint: disable=protected-access,unused-argument
diff --git a/nightlybuilds/models.py b/nightlybuilds/models.py
index 1055271..1acbedf 100644
--- a/nightlybuilds/models.py
+++ b/nightlybuilds/models.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -58,6 +57,10 @@ class Meta:
ordering = ('-build_date',)
db_table = 'nightly_build'
+ # ----------------------------------------------------------------------
+ def __str__(self):
+ return f'{self.build_date} {self.nightly_build_target}'
+
# ----------------------------------------------------------------------
def get_status(self):
return not self.status
@@ -68,7 +71,3 @@ def get_status_text(self):
return 'Built successfully'
return 'Build failed, see the logs for details'
-
- # ----------------------------------------------------------------------
- def __str__(self):
- return f'{self.build_date} {self.nightly_build_target}'
diff --git a/nightlybuilds/templatetags/nightlybuilds_tags.py b/nightlybuilds/templatetags/nightlybuilds_tags.py
index 8f34508..f1bd0db 100644
--- a/nightlybuilds/templatetags/nightlybuilds_tags.py
+++ b/nightlybuilds/templatetags/nightlybuilds_tags.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -26,10 +25,7 @@
# ----------------------------------------------------------------------
@register.simple_tag
def get_build_log(nightly_build, log_type):
- if log_type == 'Stdout':
- log = nightly_build.log_stdout
- else:
- log = nightly_build.log_stderr
+ log = nightly_build.log_stdout if log_type == 'Stdout' else nightly_build.log_stderr
if log:
logfile_path = os.path.join(BASE_DIR, nightly_build.nightly_build_target.folder, log)
diff --git a/nightlybuilds/views.py b/nightlybuilds/views.py
index e711f9c..b50dc6e 100644
--- a/nightlybuilds/views.py
+++ b/nightlybuilds/views.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -19,7 +18,7 @@
class NightlyBuildsView(ListView):
- template_name = "nightlybuilds.html"
+ template_name = 'nightlybuilds.html'
context_object_name = 'nightlybuilds'
diff --git a/pastebin/admin.py b/pastebin/admin.py
index 12f37ea..9055237 100644
--- a/pastebin/admin.py
+++ b/pastebin/admin.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
diff --git a/pastebin/api/create.py b/pastebin/api/create.py
index 35127b9..1bf9044 100644
--- a/pastebin/api/create.py
+++ b/pastebin/api/create.py
@@ -1,4 +1,3 @@
-# coding: utf-8
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -95,8 +94,8 @@ def _validate_passed_fields(self):
provided_fields = set(self._data.keys())
additional_fields = provided_fields.difference(self.valid_fields)
if additional_fields:
- raise SnippetValidationError(
- f'Invalid fields provided ({",".join(additional_fields)})')
+ msg = f'Invalid fields provided ({",".join(additional_fields)})'
+ raise SnippetValidationError(msg)
# ----------------------------------------------------------------------
def _validate_against_snippet_form(self):
diff --git a/pastebin/apps.py b/pastebin/apps.py
index e9a8c63..41e57d1 100644
--- a/pastebin/apps.py
+++ b/pastebin/apps.py
@@ -1,4 +1,3 @@
-# coding: utf-8
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -17,12 +16,12 @@
class PastebinAppConfig(AppConfig):
name = 'pastebin'
- verbose_name = "Pastebin"
+ verbose_name = 'Pastebin'
# ----------------------------------------------------------------------
def ready(self):
# Sitemap framework
# pylint: disable=import-outside-toplevel
- from geany.sitemaps import sitemap_registry, StaticSitemap
+ from geany.sitemaps import StaticSitemap, sitemap_registry
from pastebin.urls import urlpatterns
sitemap_registry.add(StaticSitemap, urlpatterns, exclude_views=['snippet_api'])
diff --git a/pastebin/forms.py b/pastebin/forms.py
index 3dbbd8b..c2adb83 100644
--- a/pastebin/forms.py
+++ b/pastebin/forms.py
@@ -60,7 +60,8 @@ def _clean_field(self, field_name):
if value:
regex = Spamword.objects.get_regex()
if regex.findall(value.lower()):
- raise forms.ValidationError('This snippet was identified as SPAM.')
+ msg = 'This snippet was identified as SPAM.'
+ raise forms.ValidationError(msg)
return value
diff --git a/pastebin/highlight.py b/pastebin/highlight.py
index 43c0c81..87dd735 100644
--- a/pastebin/highlight.py
+++ b/pastebin/highlight.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -74,7 +73,8 @@ def pygmentize(code_string, lexer_name=LEXER_DEFAULT):
if lexer_name:
lexer = get_lexer_by_name(lexer_name)
else:
- raise Exception('Unknown lexer')
+ msg = 'Unknown lexer'
+ raise ValueError(msg) # noqa: TRY301
except Exception:
lexer = PythonLexer()
diff --git a/pastebin/management/commands/cleanup_snippets.py b/pastebin/management/commands/cleanup_snippets.py
index 66ebcf9..dae6d4f 100644
--- a/pastebin/management/commands/cleanup_snippets.py
+++ b/pastebin/management/commands/cleanup_snippets.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -21,8 +20,7 @@
class Command(BaseCommand):
-
- help = 'Purges snippets that are expired'
+ help = 'Purges snippets that are expired' # noqa: A003
# ----------------------------------------------------------------------
def add_arguments(self, parser):
@@ -30,7 +28,7 @@ def add_arguments(self, parser):
'--dry-run', '-d',
action='store_true',
dest='dry_run',
- help='Don\'t do anything.')
+ help="Don't do anything.")
# ----------------------------------------------------------------------
def handle(self, *args, **options):
diff --git a/pastebin/management/commands/generate_snippets_css.py b/pastebin/management/commands/generate_snippets_css.py
index efe9db1..bf03449 100644
--- a/pastebin/management/commands/generate_snippets_css.py
+++ b/pastebin/management/commands/generate_snippets_css.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -17,7 +16,7 @@
class Command(BaseCommand):
- help = "Regenerate CSS for snippet sxntax highlighting py Pygments"
+ help = 'Regenerate CSS for snippet sxntax highlighting py Pygments' # noqa: A003
requires_system_checks = False
# ----------------------------------------------------------------------
diff --git a/pastebin/models.py b/pastebin/models.py
index 5f1bd87..3b7195a 100644
--- a/pastebin/models.py
+++ b/pastebin/models.py
@@ -11,10 +11,10 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-from datetime import timedelta
import random
import re
import time
+from datetime import timedelta
from django.core.cache import cache
from django.db import models
@@ -32,7 +32,8 @@
# ----------------------------------------------------------------------
def generate_secret_id(length=5):
- return ''.join([random.choice(CHARS) for i in range(length)]) # pylint: disable=unused-variable
+ randon_chars = random.choice(CHARS) # noqa: S311
+ return ''.join([randon_chars for i in range(length)]) # pylint: disable=unused-variable
class Snippet(models.Model):
@@ -54,6 +55,26 @@ class Snippet(models.Model):
class Meta:
ordering = ('-published',)
+ # ----------------------------------------------------------------------
+ def __str__(self):
+ return f'{self.secret_id}'
+
+ # ----------------------------------------------------------------------
+ def save(self, *args, **kwargs): # pylint: disable=signature-differs
+ if not self.pk and not self.secret_id:
+ self.secret_id = generate_secret_id()
+ if not self.published:
+ self.published = timezone.now()
+
+ self.content_highlighted = self.content
+ super().save(*args, **kwargs)
+ # invalidate cache
+ cache.delete_many([CACHE_KEY_SNIPPET_LIST_NO_CONTENT, CACHE_KEY_SNIPPET_LIST_FULL])
+
+ # ----------------------------------------------------------------------
+ def get_absolute_url(self):
+ return reverse('snippet_details', kwargs={'snippet_id': self.secret_id})
+
# ----------------------------------------------------------------------
def age(self):
age = time.mktime(self.published.timetuple())
@@ -61,14 +82,14 @@ def age(self):
# ----------------------------------------------------------------------
def _readable_delta(self, from_seconds, until_seconds=None):
- '''Returns a nice readable delta.
+ """Returns a nice readable delta.
readable_delta(1, 2) # 1 second ago
readable_delta(1000, 2000) # 16 minutes ago
readable_delta(1000, 9000) # 2 hours, 133 minutes ago
readable_delta(1000, 987650) # 11 days ago
readable_delta(1000) # 15049 days ago (relative to now)
- '''
+ """
if not until_seconds:
until_seconds = time.time()
@@ -83,12 +104,12 @@ def _readable_delta(self, from_seconds, until_seconds=None):
# show a fuzzy but useful approximation of the time delta
if delta.days:
return f'{delta.days} days ago'
- elif delta_hours:
+ if delta_hours:
return f'{delta_hours} hours ago'
- elif delta_minutes:
+ if delta_minutes:
return f'{delta_minutes} minutes ago'
- else:
- return f'{delta.seconds} seconds ago'
+
+ return f'{delta.seconds} seconds ago'
# ----------------------------------------------------------------------
def get_linecount(self):
@@ -98,39 +119,19 @@ def get_linecount(self):
def content_splitted(self):
return self.content_highlighted.splitlines()
- # ----------------------------------------------------------------------
- def save(self, *args, **kwargs): # pylint: disable=signature-differs
- if not self.pk and not self.secret_id:
- self.secret_id = generate_secret_id()
- if not self.published:
- self.published = timezone.now()
-
- self.content_highlighted = self.content
- super().save(*args, **kwargs)
- # invalidate cache
- cache.delete_many([CACHE_KEY_SNIPPET_LIST_NO_CONTENT, CACHE_KEY_SNIPPET_LIST_FULL])
-
# ----------------------------------------------------------------------
def delete(self, *args, **kwargs): # pylint: disable=signature-differs
super().delete(*args, **kwargs)
# invalidate cache
cache.delete_many([CACHE_KEY_SNIPPET_LIST_NO_CONTENT, CACHE_KEY_SNIPPET_LIST_FULL])
- # ----------------------------------------------------------------------
- def get_absolute_url(self):
- return reverse('snippet_details', kwargs={'snippet_id': self.secret_id})
-
- # ----------------------------------------------------------------------
- def __str__(self):
- return f'{self.secret_id}'
-
class SpamwordManager(models.Manager):
# ----------------------------------------------------------------------
def get_regex(self):
return re.compile(
- r'|'.join((i[1] for i in self.values_list())),
+ r'|'.join(i[1] for i in self.values_list()),
re.MULTILINE)
diff --git a/pastebin/templatetags/pastebin_tags.py b/pastebin/templatetags/pastebin_tags.py
index 815a4b7..059fbe1 100644
--- a/pastebin/templatetags/pastebin_tags.py
+++ b/pastebin/templatetags/pastebin_tags.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -41,10 +40,7 @@ def timeuntil_or_forever(snippet_expire):
@register.filter
def highlight(snippet, line_count=None):
highlighted = pygmentize(snippet.content, snippet.lexer)
- if highlighted:
- lines = highlighted.splitlines()
- else:
- lines = snippet.content.splitlines()
+ lines = highlighted.splitlines() if highlighted else snippet.content.splitlines()
if line_count:
lines = lines[:line_count]
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..7f38c9f
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,130 @@
+###
+### Ruff
+###
+[tool.ruff]
+line-length = 100
+select = [
+ "A", # flake8-builtins
+ "B", # flake8-bugbear
+ "BLE", # flake8-blind-except
+ "C4", # flake8-comprehensions
+ "C90", # mccabe
+ "DJ", # flake8-django
+ "DTZ", # flake8-datetimez
+ "E", # pycodestyle errors
+ "EM", # flake8-errmsg
+ "ERA", # eradicate
+ "EXE", # flake8-executable
+ "F", # Pyflakes
+ "FBT", # flake8-boolean-trap
+ "G", # flake8-logging-format
+ "I", # isort
+ "ICN", # flake8-import-conventions
+ "INP", # flake8-no-pep420
+ "INT", # flake8-gettext
+ "ISC", # flake8-implicit-str-concat
+ "N", # pep8-naming
+ "NPY", # NumPy-specific rules
+ "PD", # pandas-vet
+ "PGH", # pygrep-hooks
+ "PIE", # flake8-pie
+ "PL", # Pylint
+ "PT", # flake8-pytest-style
+ "PYI", # flake8-pyi
+ "Q", # flake8-quotes
+ "RET", # flake8-return
+ "RSE", # flake8-raise
+ "RUF", # Ruff-specific rules
+ "S", # flake8-bandit
+ "SIM", # flake8-simplify
+ "SLF", # flake8-self
+ "T10", # flake8-debugger
+ "T20", # flake8-print
+ "TCH", # flake8-type-checking
+ "TID", # flake8-tidy-imports
+ "TRY", # tryceratops
+ "UP", # pyupgrade
+ "W", # pycodestyle warnings
+ "YTT", # flake8-2020
+]
+
+ignore = [
+ 'DJ001', # Django model text-based fields shouldn't be nullable
+ 'FBT002', # Boolean default value in function definition
+ 'BLE001', # Do not catch blind exception: `Exception
+ 'RET504', # Unnecessary variable assignment before `return` statement
+ 'SIM114', # Combine `if` branches using logical `or` operator
+
+]
+
+fixable = ["I", "Q000"]
+unfixable = []
+
+exclude = [
+ ".eggs",
+ ".git",
+ ".ruff_cache",
+ ".tox",
+ "__pypackages__",
+ "_build",
+ "venv",
+ "migrations"
+]
+
+[tool.ruff.isort]
+lines-after-imports = 2
+
+[tool.ruff.flake8-quotes]
+inline-quotes = "single"
+
+[tool.ruff.pylint]
+allow-magic-value-types = ["bytes", "int", "str"]
+max-args = 7
+
+###
+### PyLint
+###
+[tool.pylint.main]
+ignore = ".git"
+ignore-patterns = [
+ "local_settings.py",
+ "local_settings.docker.py",
+]
+persistent = false
+load-plugins=[
+ "pylint_django",
+ "pylint.extensions.bad_builtin",
+ "pylint.extensions.check_elif",
+ "pylint.extensions.comparetozero",
+ "pylint.extensions.emptystring",
+ "pylint.extensions.mccabe",
+ "pylint.extensions.overlapping_exceptions",
+ "pylint.extensions.redefined_variable_type",
+]
+
+[tool.pylint."messages control"]
+disable = [
+ "empty-docstring",
+ "logging-format-interpolation",
+ "missing-docstring",
+ "no-else-return",
+]
+
+[tool.pylint.reports]
+output-format = "parseable"
+reports = false
+
+[tool.pylint.format]
+max-line-length = 100
+
+[tool.pylint.variables]
+dummy-variables-rgx = "_|dummy"
+
+[tool.pylint.design]
+min-public-methods = 0
+max-attributes = 10
+max-args = 7
+max-parents = 9
+
+[tool.pylint.exceptions]
+overgeneral-exceptions = []
diff --git a/static_docs/apps.py b/static_docs/apps.py
index 7548c87..f4a63d8 100644
--- a/static_docs/apps.py
+++ b/static_docs/apps.py
@@ -1,4 +1,3 @@
-# coding: utf-8
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -17,4 +16,4 @@
class StaticDocsAppConfig(AppConfig):
name = 'static_docs'
- verbose_name = "Static Docs"
+ verbose_name = 'Static Docs'
diff --git a/static_docs/generate_i18n_statistics.py b/static_docs/generate_i18n_statistics.py
index 81d786e..37d756f 100644
--- a/static_docs/generate_i18n_statistics.py
+++ b/static_docs/generate_i18n_statistics.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -12,13 +11,13 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-from json import dump, JSONEncoder
+import re
+from json import JSONEncoder, dump
from os import listdir, makedirs
from os.path import join, splitext
-from subprocess import CalledProcessError, check_output, STDOUT
+from subprocess import STDOUT, CalledProcessError, check_output
from tempfile import TemporaryDirectory
from time import time
-import re
from babel import Locale, UnknownLocaleError
@@ -96,7 +95,7 @@ def _generate(self, temp_path):
self._update_pot_file()
self._fetch_pot_stats()
self._fetch_message_catalogs()
- for self._message_catalog in self._message_catalogs:
+ for self._message_catalog in self._message_catalogs: # noqa: B020
self._update_message_catalog()
self._fetch_message_catalog_stats()
@@ -149,13 +148,13 @@ def _execute_command(self, command):
cwd=self._destination_path,
stderr=STDOUT)
output_utf8 = output.decode('utf-8')
- return output_utf8
except CalledProcessError as exc:
command_line = ' '.join(command)
error_message = exc.output.decode('utf-8')
- raise ValueError(
- f'Command: "{command_line}" exited with code {exc.returncode}: {error_message}'
- ) from exc
+ msg = f'Command: "{command_line}" exited with code {exc.returncode}: {error_message}'
+ raise ValueError(msg) from exc
+
+ return output_utf8
# ----------------------------------------------------------------------
def _fetch_pot_stats(self):
@@ -173,7 +172,8 @@ def _read_po_translation_statistics(self, filename):
fuzzy = match.group('fuzzy')
untranslated = match.group('untranslated')
else:
- raise ValueError(f'Unable to parse msgfmt output: {output}')
+ msg = f'Unable to parse msgfmt output: {output}'
+ raise ValueError(msg)
return TranslationStatistics(
translated=int(translated) if translated is not None else 0,
diff --git a/static_docs/github_client.py b/static_docs/github_client.py
index 205fdbf..0481b5b 100644
--- a/static_docs/github_client.py
+++ b/static_docs/github_client.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -12,8 +11,8 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-from base64 import standard_b64decode, standard_b64encode
import logging
+from base64 import standard_b64decode, standard_b64encode
import requests
@@ -46,13 +45,13 @@ def get_file_contents(self, filename, user=None, repository=None):
# ----------------------------------------------------------------------
def _request(self, url, status_404_expected=False):
- request_args = {'timeout': HTTP_REQUEST_TIMEOUT, 'stream': False}
+ request_args = {'stream': False}
if self._auth_token is not None:
authorization_header = self._factor_authorization_header()
request_args['headers'] = authorization_header
try:
- with requests.get(url, **request_args) as response:
+ with requests.get(url, timeout=HTTP_REQUEST_TIMEOUT, **request_args) as response:
self._log_request(response, status_404_expected)
self._log_rate_limit(response)
# error out on 4xx and 5xx status codes
@@ -60,8 +59,8 @@ def _request(self, url, status_404_expected=False):
except requests.exceptions.HTTPError as exc:
if exc.response.status_code == 404 and status_404_expected:
return None
- else:
- raise
+
+ raise
return response
diff --git a/static_docs/management/commands/generate_i18n_statistics.py b/static_docs/management/commands/generate_i18n_statistics.py
index b05f7aa..0f35593 100644
--- a/static_docs/management/commands/generate_i18n_statistics.py
+++ b/static_docs/management/commands/generate_i18n_statistics.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -19,7 +18,7 @@
class Command(BaseCommand):
- help = "Generate a JSON file with I18N statistics after updating PO files"
+ help = 'Generate a JSON file with I18N statistics after updating PO files' # noqa: A003
# ----------------------------------------------------------------------
def handle(self, *args, **options):
diff --git a/static_docs/views.py b/static_docs/views.py
index fcbd206..92b26d4 100644
--- a/static_docs/views.py
+++ b/static_docs/views.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
@@ -12,11 +11,11 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-from datetime import datetime
import json
import logging
import os.path
import re
+from datetime import datetime, timezone
from django.conf import settings
from django.core.cache import cache
@@ -25,10 +24,10 @@
from mezzanine_pagedown.filters import plain as markdown_plain
from geany.decorators import (
- cache_function,
CACHE_KEY_STATIC_DOCS_RELEASE_NOTES,
CACHE_TIMEOUT_1HOUR,
CACHE_TIMEOUT_24HOURS,
+ cache_function,
)
from static_docs.github_client import GitHubApiClient
@@ -118,7 +117,7 @@ def _parse_news_file(self):
releases.append(current_release)
current_release_notes = []
else:
- line = line.lstrip() # remove any indentation
+ line = line.lstrip() # noqa: PLW2901 - remove any indentation
if line and not line.startswith('*'):
# we got a section: make it bold and add an additional new line
# to make Markdown recognise the following lines as list
@@ -165,7 +164,7 @@ def _get_release_notes_for_version(self, releases, version=None):
release = rel
break
else:
- raise Http404()
+ raise Http404
# convert the selected release (the one we want to display) to Markdown
release.release_notes = markdown_plain(release.release_notes)
@@ -185,7 +184,8 @@ def _get_release_from_github(self, version=None):
return None
# adapt date
- release_datetime = datetime.strptime(github_release['published_at'], '%Y-%m-%dT%H:%M:%SZ')
+ release_datetime = datetime.strptime(github_release['published_at'], '%Y-%m-%dT%H:%M:%SZ')\
+ .astimezone(tz=timezone.utc)
release_date = release_datetime.strftime('%B %d, %Y')
release = ReleaseDto()
@@ -207,7 +207,7 @@ class ToDoView(StaticDocsView):
Grab the TODO file from GIT master via Github API, parse it and send it back to the template
"""
- template_name = "pages/documentation/todo.html"
+ template_name = 'pages/documentation/todo.html'
# ----------------------------------------------------------------------
def get_context_data(self, **kwargs):
@@ -229,15 +229,15 @@ def _parse_news_file(self):
class I18NStatisticsView(TemplateView):
- template_name = "pages/i18n.html"
+ template_name = 'pages/i18n.html'
# ----------------------------------------------------------------------
def get_context_data(self, **kwargs):
i18n_statistics = self._get_i18n_statistics()
context = super().get_context_data(**kwargs)
context['i18n_statistics'] = i18n_statistics
- context['generated_datetime'] = datetime.utcfromtimestamp(
- i18n_statistics['generated_timestamp'])
+ context['generated_datetime'] = datetime.fromtimestamp(
+ i18n_statistics['generated_timestamp'], tz=timezone.utc)
context['static_docs_geany_destination_url'] = settings.STATIC_DOCS_GEANY_DESTINATION_URL
return context
@@ -256,7 +256,7 @@ class ThemesView(StaticDocsView):
Fetch the Geany-Themes index from https://github.com/geany/geany-themes/tree/master/index
"""
- template_name = "pages/download/themes.html"
+ template_name = 'pages/download/themes.html'
# ----------------------------------------------------------------------
def get_context_data(self, **kwargs):
diff --git a/tox.ini b/tox.ini
index 6e6ca77..f1ba9e0 100644
--- a/tox.ini
+++ b/tox.ini
@@ -15,21 +15,19 @@
skip_missing_interpreters = true
skipsdist = true
envlist =
- safety,py37,py38,py39,py310
+ safety,py39,py310,py311
geany_modules = geany latest_version news nightlybuilds pastebin static_docs
[testenv]
deps =
- flake8
- isort
+ ruff
pylint
pylint-django
-r{toxinidir}/requirements.txt
commands =
- {envbindir}/flake8 {[tox]geany_modules}
- {envbindir}/isort --check-only --diff {[tox]geany_modules}
- {envbindir}/pylint --rcfile=tox.ini --django-settings-module=geany.settings {[tox]geany_modules}
+ {envbindir}/ruff check {[tox]geany_modules}
+ {envbindir}/pylint --rcfile=pyproject.toml --django-settings-module=geany.settings {[tox]geany_modules}
[testenv:safety]
deps =
@@ -37,61 +35,3 @@ deps =
-r{toxinidir}/requirements.txt
commands =
{envbindir}/safety check
-
-[flake8]
-exclude = build,.git,docs,migrations,local_settings.py,local_settings.docker.py
-ignore = E127,E128,
-max-line-length = 100
-
-[isort]
-line_length = 100
-indent = 4
-multi_line_output = 3
-length_sort = false
-force_alphabetical_sort_within_sections = true
-sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
-lines_after_imports = 2
-from_first = true
-include_trailing_comma = true
-skip = local_settings.py,local_settings.docker.py
-
-# the following sections are for pylint
-[pylint.main]
-ignore=.git
-ignore-patterns=local_settings.py,local_settings.docker.py
-persistent=no
-load-plugins=
- pylint_django,
- pylint.extensions.bad_builtin,
- pylint.extensions.check_elif,
- pylint.extensions.comparetozero,
- pylint.extensions.emptystring,
- pylint.extensions.mccabe,
- pylint.extensions.overlapping_exceptions,
- pylint.extensions.redefined_variable_type
-
-[pylint]
-disable=
- empty-docstring,
- logging-format-interpolation,
- missing-docstring,
- no-else-return
-
-[pylint.reports]
-output-format=parseable
-reports=no
-
-[pylint.format]
-max-line-length=100
-
-[pylint.variables]
-dummy-variables-rgx=_|dummy
-
-[pylint.design]
-min-public-methods=0
-max-attributes=10
-max-args=7
-max-parents=9
-
-[pylint.exceptions]
-overgeneral-exceptions=
diff --git a/urlshortener/apps.py b/urlshortener/apps.py
index c5fea0b..33d31b0 100644
--- a/urlshortener/apps.py
+++ b/urlshortener/apps.py
@@ -1,4 +1,3 @@
-# coding: utf-8
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
diff --git a/urlshortener/urls.py b/urlshortener/urls.py
index 775748c..6f58577 100644
--- a/urlshortener/urls.py
+++ b/urlshortener/urls.py
@@ -1,4 +1,3 @@
-# coding: utf-8
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
diff --git a/urlshortener/views.py b/urlshortener/views.py
index 7723b96..42ed757 100644
--- a/urlshortener/views.py
+++ b/urlshortener/views.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
diff --git a/uwsgi.ini b/uwsgi.ini
index 06c6347..7fc6cea 100644
--- a/uwsgi.ini
+++ b/uwsgi.ini
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# LICENCE: This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or