"]
-packages = [
- { include = "aurora", from = "src" },
+readme = "README.md"
+license = {text = "MIT"}
+
+authors = [
+ {name = "sax", email = "s.apostolico@gmail.com"},
+ {name = "Domenico DiNicola", email = "dom.dinicola@gmail.com"},
+]
+requires-python = ">=3.12"
+dependencies = [
+ "Faker",
+ "Markdown",
+ "Pillow",
+ "beautifulsoup4",
+ "celery",
+ "channels-redis",
+ "channels[daphne]",
+ "cryptography",
+ "django-admin-extra-buttons",
+ "django-admin-ordering",
+ "django-admin-sync",
+ "django-adminactions",
+ "django-adminfilters",
+ "django-anymail[mailjet]",
+ "django-appconf",
+ "django-bitfield",
+ "django-click",
+ "django-concurrency",
+ "django-constance",
+ "django-cors-headers",
+ "django-csp",
+ "django-debug-toolbar",
+ "django-environ",
+ "django-filter",
+ "django-flags",
+ "django-front-door",
+ "django-hijack",
+ "django-import-export",
+ "django-jsoneditor",
+ "django-mdeditor",
+ "django-mptt",
+ "django-picklefield",
+ "django-pwa",
+ "django-redis",
+ "django-regex",
+ "django-reversion",
+ "django-reversion-compare",
+ "django-simple-captcha",
+ "django-simple-math-captcha",
+ "django-smart-admin",
+ "django-smart-env",
+ "django-strategy-field",
+ "django-sysinfo",
+ "django-tinymce",
+ "django<5.1",
+ "djangorestframework",
+ "djangorestframework-datatables",
+ "htmlmin",
+ "jmespath",
+ "jsonpickle",
+ "mini-racer",
+ "natural-keys",
+ "psycopg2-binary",
+ "py-mini-racer",
+ "pycryptodome",
+ "pygments",
+ "pyquery",
+ "qrcode",
+ "sentry-sdk",
+ "setuptools>=75.6.0",
+ "social-auth-app-django",
+ "soupsieve",
+ "sqlparse",
+ "uwsgi",
]
-[tool.poetry.dependencies]
-Faker = "^14.0.0"
-Markdown = "^3.3.6"
-Pillow = "^9.0.1"
-celery = "^5.2.6"
-cryptography = "*"
-django = "^3"
-django-admin-extra-buttons = ">=1.5.6"
-django-admin-ordering = "*"
-django-admin-sync = ">=0.7.1"
-django-adminactions = "^2"
-django-adminfilters = "^2"
-django-appconf = "^1.0.5"
-django-bitfield = "^2.1"
-django-click = "^2.3"
-django-concurrency = "^2.4"
-django-constance = "^2.8"
-django-cors-headers = "^3.11.0"
-django-csp = "^3.7"
-django-environ = "^0.9"
-django-flags = "^5"
-django-import-export = "*"
-django-jsoneditor = "^0.2"
-django-picklefield = "^3.0"
-django-redis = "^5.2.0"
-django-regex = "^0.5"
-django-reversion = "^5.0"
-django-reversion-compare = "^0.15"
-django-simple-captcha = "^0.5.17"
-django-simple-math-captcha = "^2"
-django-smart-admin = ">=2"
-django-strategy-field = "^3"
-django-sysinfo = "^2"
-django_regex = "*"
-djangorestframework = "^3"
-htmlmin = "^0.1"
-jmespath = "^1.0"
-jsonpickle="^2.1"
-natural-keys = "^2.0.0"
-psycopg2-binary = "^2.9.3"
-py-mini-racer = "^0.6.0"
-pycryptodome = "^3.14.1"
-#pyduktape2 = "^0.4.3"
-python = "^3.9"
-qrcode = "^7.3.1"
-sentry-sdk = "^1.5"
-setuptools = "^65.5.0"
-social-auth-app-django = "^5.0.0"
-sqlparse = "^0.4.2"
-django-tinymce = "^3.5.0"
-django-pwa = "^1.0.10"
-django-mptt = "^0.14.0"
-django-hijack = "^3.2.6"
-channels = {extras = ["daphne"], version = "^4.0.0"}
-channels-redis = "^4.0.0"
-django-mdeditor = "^0.1.20"
-djangorestframework-datatables = "^0.7.0"
-django-filter = "^22.1"
-django-front-door = "^0.10.0"
-django-debug-toolbar = "^3"
-pygments = "^2.14.0"
-soupsieve = "^2.4"
-pyquery = "^2.0.0"
-beautifulsoup4 = "^4.12.0"
+[uv]
+package = true
-[tool.poetry.dev-dependencies]
-black = "^22.1.0"
-coverage = "*"
-django-webtest = "^1.9.7"
-factory_boy = "*"
-flake8 = "*"
-freezegun = "^1.2.2"
-ipython = "*"
-isort = "^5.6.4"
-pdbpp = "*"
-pre-commit = "*"
-pyflakes = "*"
-pytest = "*"
-pytest-asyncio = "*"
-pytest-coverage = "^0.0"
-pytest-django = "^4.1.0"
-pytest-echo = "*"
-pytest-pythonpath = "^0.7.3"
-pytest-selenium = "^2.0"
-tox = "*"
-watchdog = "^2.1.6"
-django-stubs = {extras = ["compatible-mypy"], version = "^1.16.0"}
+[project.optional-dependencies]
+docs = [
+ "mkdocs",
+ "mkdocs-material",
+ "mkdocs-awesome-pages-plugin",
+ "mkdocstrings-python",
+ "mkdocs-gen-files",
+]
+
+[tool.uv]
+dev-dependencies = [
+ "black",
+ "coverage",
+ "django-stubs[compatible-mypy]",
+ "django-webtest",
+ "factory-boy",
+ "flake8",
+ "flake8-html",
+ "freezegun",
+ "ipython",
+ "isort",
+ "pdbpp",
+ "pre-commit",
+ "pyflakes",
+ "pytest-asyncio",
+ "pytest-coverage",
+ "pytest-django",
+ "pytest-echo",
+ "pytest-html",
+ "pytest-pythonpath",
+ "pytest-selenium",
+ "pytest-variables",
+ "pytest-xdist",
+ "pytest",
+ "tox",
+ "watchdog",
+]
[build-system]
-requires = ["poetry-core>=1.1.4"]
-build-backend = "poetry.core.masonry.api"
+requires = ["setuptools"]
+build-backend = "setuptools.build_meta"
-[tool.isort]
-profile = "black"
+[tool.setuptools.package-dir]
+"" = "src"
+
+[tool.setuptools]
+packages = ["aurora", "dbtemplates"]
[tool.black]
line-length = 120
-target-version = ['py39']
include = '\.pyi?$'
exclude = '''
/(
- \.toml
- |\.sh
- |\.git
- |\.ini
- |Dockerfile
+ \.git
+ | \.pytest_cache
+ | \.tox
+ | \.venv
+ | ~build
+ | build
+ | ops
+ | migrations
)/
'''
+
+[tool.isort]
+profile = "black"
+line_length = 120
+default_section = "THIRDPARTY"
+known_first_party = []
+known_django = "django"
+sections = ["FUTURE","STDLIB","DJANGO","THIRDPARTY","FIRSTPARTY","LOCALFOLDER"]
+include_trailing_comma = true
+skip = ["migrations", "snapshots", ".venv"]
+
+
+[tool.nitpick]
+ style = [
+ "github://unicef/hope-code-conventions@main/django/django.toml"
+ ]
+ cache = "1 day"
diff --git a/pytest.ini b/pytest.ini
index 343cc04b..efb5ff45 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -4,7 +4,7 @@ django_find_project = true
DJANGO_SETTINGS_MODULE = aurora.config.settings
log_format = %(asctime)s %(levelname)s %(message)s
log_level = CRITICAL
-python_paths = tests/extras src/
+pythonpath = tests/extras src/
log_date_format = %Y-%m-%d %H:%M:%S
addopts =
--cov=aurora
@@ -12,6 +12,7 @@ addopts =
--cov-report=xml
--cov-config=./tests/.coveragerc
--reuse-db
+ --maxfail=20
--tb=short
--capture=no
--echo-version django
@@ -26,5 +27,3 @@ markers =
python_files=test_*.py
filterwarnings =
ignore::DeprecationWarning
- ignore::django.utils.deprecation.RemovedInDjango40Warning
- ignore::django.utils.deprecation.RemovedInDjango41Warning
diff --git a/ruff.toml b/ruff.toml
new file mode 100644
index 00000000..941d1975
--- /dev/null
+++ b/ruff.toml
@@ -0,0 +1,91 @@
+target-version = "py313"
+line-length = 120
+
+[lint.isort]
+case-sensitive = true
+
+[lint]
+select = [
+ "A", # prevent using keywords that clobber python builtins
+# "ANN", # flake8 annotations
+ "B", # bugbear: security warnings
+ "BLE", # blind exceptions
+ "C4", # flake8-comprehensions
+ "C90", # McCabe complexity
+ "COM", # flake8-commas
+ "D", # pydocstyle
+ "DJ", # flake8-django
+ "E", # pycodestylex
+ "E4", "E7", "E9",
+ "ERA", # eradicate
+ "F", # pyflakes
+ "FLY", # flynt
+ "FURB", # refurb
+ "I", # isort
+ "ICN", # flake8-import-conventions
+ "ISC", # implicit string concatenation
+ "N", # Pep* naming
+ "PERF", # perflint
+ "PIE", # flake8-pie
+ "PL", # PyLint
+ "PT", # flake8-pytest-style
+ "Q", # flake8-quotes
+ "R", # PyLint Refactor
+ "RET", # flake8-return
+ "S", # bandit,
+ "SIM", # flake8-simplify
+ "T10", # flake8-debugger
+ "T20", # flake8-print
+ "TC", # flake8-type-checking
+ "UP", # pyupgrade
+ "W", # pycodestyle warnings
+ "YTT", # flake8 2020
+]
+extend-select = ["UP", ]
+ignore = [
+ "ANN401",
+ "B904", # raise-without-from-inside-except: syntax not compatible with py2
+ "COM812",
+ "D100", # Missing docstring in public module
+ "D101", # Missing docstring in public class
+ "D102", # Missing docstring in public method
+ "D103", # Missing docstring in public function
+ "D104", # Missing docstring in public package
+ "D105", # Missing docstring in magic method
+ "D106", # Missing docstring in public nested class
+ "D107", # Missing docstring in `__init__`
+ "D203", # one-blank-line-before-class
+ "D212", # multi-line-summary-first-line
+ "D213", # multi-line-summary-second-line
+ "E731", # lambda-assignment: lambdas are substential in maintenance of py2/3 codebase
+ "ISC001", # conflicts with ruff format command
+ "RUF005", # collection-literal-concatenation: syntax not compatible with py2
+ "RUF012", # mutable-class-default: typing is not available for py2
+ "I001", # unsorted imports https://docs.astral.sh/ruff/rules/unsorted-imports/#unsorted-imports-i001
+ "UP037", # [*] Remove quotes from type annotation
+ "UP035", # Import from `collections.abc` instead: `Sequence`
+ "UP031", # Use format specifiers instead of percent format
+ "SIM108", # Use ternary operator instead of...
+ "PLR2004", # Magic value used in comparison
+ "DJ001", # Avoid using `null=True` on string-based fields such as `CharField`
+
+ # todos
+ "BLE001", # blind exception
+ "S324", # insecure md5
+ "C901", # too complex
+ "PLR0912",
+ "PLR0913",
+ "PLR0915",
+ "PLW0603"
+]
+
+[format]
+quote-style = "double"
+indent-style = "space"
+skip-magic-trailing-comma = false
+line-ending = "auto"
+
+[lint.per-file-ignores]
+"tests/**.py" = ["S101", "PLR2004", "S", "SIM117", "D", "UP", "PLR0913", "ANN", "N999"]
+"src/dbtemplates/test**.py" = ["S101", "PLR2004", "S", "SIM117", "D", "UP", "PLR0913", "ANN", "N999", "PT009"]
+"src/**/versioning/**.py" = ["N999", ]
diff --git a/src/aurora/__init__.py b/src/aurora/__init__.py
index 78cc22f5..2bec683e 100644
--- a/src/aurora/__init__.py
+++ b/src/aurora/__init__.py
@@ -1,31 +1 @@
-import os
-from functools import lru_cache
-from subprocess import STDOUT
-
-VERSION = os.environ.get("VERSION", "")
-
-
-@lru_cache(1)
-def get_full_version(git_commit=True):
- commit = ""
- if git_commit:
- import subprocess
-
- try:
- res = subprocess.check_output(["git", "rev-parse", "--short", "HEAD"], stderr=STDOUT)
- commit = "-" + res.decode("utf8")[:-1]
- except (subprocess.CalledProcessError, FileNotFoundError): # pragma: no-cover
- pass
-
- return f"{VERSION}{commit}"
-
-
-@lru_cache(1)
-def get_git_status(clean="(nothing to commit)", dirty="(uncommitted changes)"):
- import subprocess
-
- try:
- uncommited = subprocess.check_output(["git", "status", "-s"], stderr=STDOUT)
- return dirty if uncommited else clean
- except (subprocess.CalledProcessError, FileNotFoundError): # pragma: no-cover
- return ""
+VERSION = __version__ = "2.0.0"
diff --git a/src/aurora/administration/admin.py b/src/aurora/administration/admin.py
deleted file mode 100644
index 8886a171..00000000
--- a/src/aurora/administration/admin.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# from admin_extra_buttons.decorators import button
-# from adminactions.helpers import AdminActionPermMixin
-# from django.utils.translation import gettext_lazy as _
-# from hijack.templatetags.hijack import can_hijack
-# from smart_admin.smart_auth.admin import GroupAdmin, UserAdmin
-#
-# from aurora.administration.hijack import impersonate
-# from aurora.core.admin_sync import SyncMixin
-# from aurora.core.utils import is_root
-#
-#
-# class AuroraUserAdmin(AdminActionPermMixin, UserAdmin):
-# fieldsets = (
-# (None, {"fields": ("username", "password")}),
-# (_("Personal info"), {"fields": ("first_name", "last_name", "email")}),
-# (
-# _("Permissions"),
-# {
-# "fields": (
-# "is_active",
-# "is_staff",
-# "is_superuser",
-# "groups",
-# ),
-# },
-# ),
-# (_("Important dates"), {"fields": ("last_login", "date_joined")}),
-# )
-#
-# @button(permission=lambda req, obj, **kw: is_root(req) and can_hijack(req.user, obj))
-# def hijack(self, request, pk):
-# hijacked = self.get_object(request, pk)
-# impersonate(request, hijacked)
-#
-#
-# class AuroraUserAdminTemp(AuroraUserAdmin):
-# list_display = ("username", "email", "first_name", "last_name", "is_staff", "is_superuser", "user")
-#
-# def get_list_display(self, request):
-# return super().get_list_display(request)
-#
-#
-# class AuroraGroupAdmin(AdminActionPermMixin, SyncMixin, GroupAdmin):
-# pass
diff --git a/src/aurora/administration/apps.py b/src/aurora/administration/apps.py
index 72c948fb..2cfee88e 100644
--- a/src/aurora/administration/apps.py
+++ b/src/aurora/administration/apps.py
@@ -8,6 +8,7 @@ class AuroraAdminConfig(AppConfig):
def ready(self):
super().ready()
from django.contrib.admin import site
+
from smart_admin.console import (
panel_email,
panel_error_page,
diff --git a/src/aurora/administration/filters.py b/src/aurora/administration/filters.py
index ec305bf4..d1dc423d 100644
--- a/src/aurora/administration/filters.py
+++ b/src/aurora/administration/filters.py
@@ -1,10 +1,11 @@
-from adminfilters.autocomplete import get_real_field
-from adminfilters.mixin import MediaDefinitionFilter, SmartFieldListFilter
from django.conf import settings
from django.contrib.admin.widgets import SELECT2_TRANSLATIONS
from django.urls import reverse
from django.utils.translation import get_language
+from adminfilters.autocomplete import get_real_field
+from adminfilters.mixin import MediaDefinitionFilter, SmartFieldListFilter
+
from aurora.core.version_media import VersionMedia
@@ -76,6 +77,5 @@ def get_title(self):
if not self.can_negate and self.negated:
if self.negated_title:
return self.negated_title
- else:
- return f"not {self.title}"
+ return f"not {self.title}"
return self.filter_title or self.title
diff --git a/src/aurora/administration/forms.py b/src/aurora/administration/forms.py
index e4bf9942..dd18f9c8 100644
--- a/src/aurora/administration/forms.py
+++ b/src/aurora/administration/forms.py
@@ -1,18 +1,28 @@
import base64
import urllib.parse
-import sqlparse
from django import forms
from django.core.exceptions import ValidationError
+import sqlparse
+
class ImportForm(forms.Form):
file = forms.FileField()
class ExportForm(forms.Form):
- APPS = ("core", "registration", "i18n", "constance", "counters", "flatpages", "security", "dbtemplates")
- apps = forms.MultipleChoiceField(choices=zip(APPS, APPS), widget=forms.CheckboxSelectMultiple())
+ APPS = (
+ "core",
+ "registration",
+ "i18n",
+ "constance",
+ "counters",
+ "flatpages",
+ "security",
+ "dbtemplates",
+ )
+ apps = forms.MultipleChoiceField(choices=zip(APPS, APPS, strict=True), widget=forms.CheckboxSelectMultiple())
class SQLForm(forms.Form):
diff --git a/src/aurora/administration/hijack.py b/src/aurora/administration/hijack.py
index c1848d42..ec936060 100644
--- a/src/aurora/administration/hijack.py
+++ b/src/aurora/administration/hijack.py
@@ -1,4 +1,5 @@
from django.contrib.auth import login
+
from hijack import signals
from hijack.templatetags.hijack import can_hijack
from hijack.views import get_used_backend, keep_session_age
diff --git a/src/aurora/administration/mixin.py b/src/aurora/administration/mixin.py
index 3c7cbc50..2a8a0cb3 100644
--- a/src/aurora/administration/mixin.py
+++ b/src/aurora/administration/mixin.py
@@ -3,13 +3,14 @@
import tempfile
from pathlib import Path
-from admin_extra_buttons.decorators import button
-from admin_extra_buttons.mixins import ExtraButtonsMixin
-from concurrency.api import disable_concurrency
from django.contrib import messages
from django.core.management import call_command
from django.http import JsonResponse
+from admin_extra_buttons.decorators import button
+from admin_extra_buttons.mixins import ExtraButtonsMixin
+from concurrency.api import disable_concurrency
+
from aurora.core.utils import render
from .forms import ImportForm
@@ -54,7 +55,11 @@ def loaddata(self, request):
finally:
fixture.unlink()
except Exception as e:
- self.message_user(request, f"{e.__class__.__name__}: {e} {out.getvalue()}", messages.ERROR)
+ self.message_user(
+ request,
+ f"{e.__class__.__name__}: {e} {out.getvalue()}",
+ messages.ERROR,
+ )
else:
ctx["form"] = form
else:
diff --git a/src/aurora/administration/panels.py b/src/aurora/administration/panels.py
index 85b60b12..d872548c 100644
--- a/src/aurora/administration/panels.py
+++ b/src/aurora/administration/panels.py
@@ -4,8 +4,6 @@
import tempfile
from pathlib import Path
-import sqlparse
-from concurrency.api import disable_concurrency
from django.contrib import messages
from django.core.exceptions import PermissionDenied
from django.core.management import call_command
@@ -13,10 +11,18 @@
from django.http import JsonResponse
from django.shortcuts import render
+import sqlparse
+from concurrency.api import disable_concurrency
+
from .. import VERSION
from ..core.utils import is_root
-from ..security.models import UserProfile
+
from .forms import ExportForm, ImportForm, SQLForm
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ from ..security.models import UserProfile
+
logger = logging.getLogger(__name__)
@@ -67,7 +73,11 @@ def panel_loaddata(self, request):
finally:
fixture.unlink()
except Exception as e:
- messages.add_message(request, messages.ERROR, f"{e.__class__.__name__}: {e} {out.getvalue()}")
+ messages.add_message(
+ request,
+ messages.ERROR,
+ f"{e.__class__.__name__}: {e} {out.getvalue()}",
+ )
else:
context["form"] = form
@@ -142,7 +152,6 @@ def panel_sql(self, request, extra_context=None):
if form.is_valid():
try:
cmd = form.cleaned_data["command"]
- # stm = urllib.parse.unquote(base64.b64decode(cmd).decode())
response["stm"] = sqlparse.format(cmd)
if is_root(request):
conn = connections[DEFAULT_DB_ALIAS]
@@ -159,7 +168,6 @@ def panel_sql(self, request, extra_context=None):
else:
response["error"] = str(form.errors)
return JsonResponse(response)
- else:
- form = SQLForm()
+ form = SQLForm()
context["form"] = form
return render(request, "admin/panels/sql.html", context)
diff --git a/src/aurora/administration/templates/admin/_footer.html b/src/aurora/administration/templates/admin/_footer.html
index 1b91ca92..d222438f 100644
--- a/src/aurora/administration/templates/admin/_footer.html
+++ b/src/aurora/administration/templates/admin/_footer.html
@@ -1,6 +1,6 @@
{% load static %}
- {{ project.build_date }} - {% include "i18n/_select_language.html" with languages=project.languages %}
+ Aurora {{ project.version }} - {{ project.build_date }} - {{ project.commit }} - {% include "i18n/_select_language.html" with languages=project.languages %}
{% csrf_token %}
- {% endblock %}
+ {% endblock script %}