chore: ruff fixes and formatting
This commit is contained in:
27 files changed
+444
-313
No files matched your search
@@ -8,3 +8,4 @@ node_modules/
|
||||
.sass-cache/
|
||||
staticfiles/
|
||||
mailadmin/static/mailadmin/dist/
|
||||
__pycache__/
|
||||
@@ -1,12 +1,12 @@
|
||||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
# - repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
# rev: 'v0.11.7'
|
||||
# hooks:
|
||||
# - id: ruff
|
||||
# args: [--fix, --exit-non-zero-on-fix]
|
||||
# - id: ruff-format
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: 'v0.11.7'
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
- id: ruff-format
|
||||
- repo: https://github.com/adamchainz/django-upgrade
|
||||
rev: "1.24.0"
|
||||
hooks:
|
||||
|
||||
Vendored
+22
-16
@@ -7,27 +7,33 @@ from invoke import task
|
||||
@task()
|
||||
def deploy(c):
|
||||
"""Make sure proxy_user is set to your neuf username."""
|
||||
project_path = '/var/www/neuf.no/mailadmin'
|
||||
proxy_user = os.getenv('DEPLOY_USER', os.getenv('USER'))
|
||||
project_path = "/var/www/neuf.no/mailadmin"
|
||||
proxy_user = os.getenv("DEPLOY_USER", os.getenv("USER"))
|
||||
|
||||
c = Connection(host='gitdeploy@dreamcast.neuf.no', gateway=Connection('login.neuf.no', user=proxy_user))
|
||||
c = Connection(
|
||||
host="gitdeploy@dreamcast.neuf.no",
|
||||
gateway=Connection("login.neuf.no", user=proxy_user),
|
||||
)
|
||||
|
||||
with c.cd(project_path), c.prefix('source {}/venv/bin/activate'.format(project_path)):
|
||||
c.run('git pull') # Get source
|
||||
c.run('pip install -U pip')
|
||||
c.run('pip install -r requirements.txt') # install deps in virtualenv
|
||||
with c.cd('mailadmin/static/mailadmin'): # install and compile frontend deps
|
||||
c.run('npm i')
|
||||
c.run('npm run build')
|
||||
c.run('python manage.py collectstatic --noinput -i node_modules') # Collect static
|
||||
c.run('python manage.py migrate') # Run DB migrations
|
||||
with (
|
||||
c.cd(project_path),
|
||||
c.prefix(f"source {project_path}/venv/bin/activate"),
|
||||
):
|
||||
c.run("git pull") # Get source
|
||||
c.run("pip install -U pip")
|
||||
c.run("pip install -r requirements.txt") # install deps in virtualenv
|
||||
with c.cd("mailadmin/static/mailadmin"): # install and compile frontend deps
|
||||
c.run("npm i")
|
||||
c.run("npm run build")
|
||||
c.run("python manage.py collectstatic --noinput -i node_modules") # Collect static
|
||||
c.run("python manage.py migrate") # Run DB migrations
|
||||
|
||||
# Reload gunicorn
|
||||
c.sudo('/usr/bin/supervisorctl pid lister.neuf.no | xargs kill -HUP', shell=False)
|
||||
c.sudo("/usr/bin/supervisorctl pid lister.neuf.no | xargs kill -HUP", shell=False)
|
||||
|
||||
|
||||
@task
|
||||
def build(c):
|
||||
with c.cd('mailadmin/static/mailadmin'):
|
||||
c.run('npm i')
|
||||
c.run('npm run build')
|
||||
with c.cd("mailadmin/static/mailadmin"):
|
||||
c.run("npm i")
|
||||
c.run("npm run build")
|
||||
+4
-3
@@ -10,9 +10,10 @@ class PrefixInline(admin.TabularInline):
|
||||
|
||||
@admin.register(OrgUnit)
|
||||
class OrgUnitAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'is_active')
|
||||
list_filter = ('is_active',)
|
||||
filter_horizontal = ('admin_groups', 'member_groups')
|
||||
list_display = ("name", "is_active")
|
||||
list_filter = ("is_active",)
|
||||
filter_horizontal = ("admin_groups", "member_groups")
|
||||
inlines = [PrefixInline]
|
||||
|
||||
|
||||
admin.site.register(Prefix)
|
||||
+30
-31
@@ -1,38 +1,44 @@
|
||||
import logging
|
||||
|
||||
import requests
|
||||
from django.conf import settings
|
||||
import logging
|
||||
from requests.exceptions import ConnectionError
|
||||
from requests.auth import HTTPBasicAuth
|
||||
from rest_framework import exceptions
|
||||
from rest_framework import status
|
||||
from requests.exceptions import ConnectionError
|
||||
from rest_framework import exceptions, status
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DPDAPIException(exceptions.APIException):
|
||||
status_code = 503
|
||||
detail = 'Unknown error.'
|
||||
detail = "Unknown error."
|
||||
|
||||
def __init__(self, status_code, detail):
|
||||
self.status_code = status_code
|
||||
self.detail = detail
|
||||
|
||||
|
||||
class DjangoPostfixDovecotAPI(object):
|
||||
class DjangoPostfixDovecotAPI:
|
||||
"""
|
||||
django-postfix-dovecot-api
|
||||
|
||||
https://git.neuf.no/edb/django-postfix-dovecot-api#tab-readme
|
||||
|
||||
"""
|
||||
def __init__(self, base_url=settings.DPD_API_URL, username=settings.DPD_API_USERNAME,
|
||||
password=settings.DPD_API_PASSWORD):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
base_url=settings.DPD_API_URL,
|
||||
username=settings.DPD_API_USERNAME,
|
||||
password=settings.DPD_API_PASSWORD,
|
||||
):
|
||||
self.base_url = base_url
|
||||
self.username = username
|
||||
self.password = password
|
||||
|
||||
def _request(self, method, path, params=None, json=None):
|
||||
headers = {'content-type': 'application/json'}
|
||||
url = "{}{}".format(self.base_url, path)
|
||||
headers = {"content-type": "application/json"}
|
||||
url = f"{self.base_url}{path}"
|
||||
|
||||
try:
|
||||
r = requests.request(
|
||||
@@ -41,20 +47,21 @@ class DjangoPostfixDovecotAPI(object):
|
||||
params=params,
|
||||
auth=HTTPBasicAuth(self.username, self.password),
|
||||
json=json,
|
||||
headers=headers)
|
||||
headers=headers,
|
||||
)
|
||||
except ConnectionError as e:
|
||||
msg = 'mxapi at {} unavailable {}'.format(url, str(e))
|
||||
msg = f"mxapi at {url} unavailable {e!s}"
|
||||
logger.warning(msg)
|
||||
raise DPDAPIException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail={'error': msg})
|
||||
raise DPDAPIException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail={"error": msg})
|
||||
|
||||
try:
|
||||
r.raise_for_status()
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.warning('mxapi errored {}'.format(e.response))
|
||||
logger.warning(f"mxapi errored {e.response}")
|
||||
try:
|
||||
raise DPDAPIException(status_code=e.response.status_code, detail=e.response.json())
|
||||
except ValueError:
|
||||
raise DPDAPIException(status_code=e.response.status_code, detail='mxapi fatal error')
|
||||
raise DPDAPIException(status_code=e.response.status_code, detail="mxapi fatal error")
|
||||
|
||||
try:
|
||||
ret = r.json()
|
||||
@@ -64,33 +71,25 @@ class DjangoPostfixDovecotAPI(object):
|
||||
return ret
|
||||
|
||||
def create_aliases(self, aliases):
|
||||
return self._request('POST', '/aliases/create_bulk/', json=aliases)
|
||||
return self._request("POST", "/aliases/create_bulk/", json=aliases)
|
||||
|
||||
def delete_aliases(self, aliases):
|
||||
return self._request('DELETE', '/aliases/delete_bulk/', json=aliases)
|
||||
return self._request("DELETE", "/aliases/delete_bulk/", json=aliases)
|
||||
|
||||
def list_aliases_regex(self, regex):
|
||||
""" Limit aliases by regular expression and domain name
|
||||
"""Limit aliases by regular expression and domain name
|
||||
:param regex: Regular expression filtering out aliases
|
||||
"""
|
||||
params = {
|
||||
'domain__name': settings.NEUF_EMAIL_DOMAIN_NAME,
|
||||
'source__iregex': regex
|
||||
"domain__name": settings.NEUF_EMAIL_DOMAIN_NAME,
|
||||
"source__iregex": regex,
|
||||
}
|
||||
return self._request(
|
||||
'GET',
|
||||
'/aliases/',
|
||||
params=params
|
||||
)
|
||||
return self._request("GET", "/aliases/", params=params)
|
||||
|
||||
def list_domains(self, name=None):
|
||||
params = {}
|
||||
|
||||
if name is not None:
|
||||
params['name'] = name
|
||||
params["name"] = name
|
||||
|
||||
return self._request(
|
||||
'GET',
|
||||
'/domains/',
|
||||
params=params
|
||||
)
|
||||
return self._request("GET", "/domains/", params=params)
|
||||
@@ -17,14 +17,14 @@ class LDAPEmailBackend(LDAPBackend):
|
||||
the user's DN and ldap_user.attrs contains all of their LDAP attributes.
|
||||
"""
|
||||
model = self.get_user_model()
|
||||
username_field = getattr(model, 'USERNAME_FIELD', 'username')
|
||||
username_field = getattr(model, "USERNAME_FIELD", "username")
|
||||
|
||||
kwargs = {
|
||||
username_field + '__iexact': ldap_user.attrs['uid'][0],
|
||||
'defaults': {
|
||||
username_field: ldap_user.attrs['uid'][0].lower(),
|
||||
'email': email
|
||||
}
|
||||
username_field + "__iexact": ldap_user.attrs["uid"][0],
|
||||
"defaults": {
|
||||
username_field: ldap_user.attrs["uid"][0].lower(),
|
||||
"email": email,
|
||||
},
|
||||
}
|
||||
|
||||
return model.objects.get_or_create(**kwargs)
|
||||
return model.objects.get_or_create(**kwargs)
|
||||
Whitespace-only changes.
Whitespace-only changes.
@@ -1,28 +0,0 @@
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.contrib.auth.models import Group
|
||||
|
||||
import requests
|
||||
|
||||
from mailadmin.models import OrgUnit
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Adds groups and OrgUnits from inside.studentersamfundet.no.'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
r = requests.get('https://inside.studentersamfundet.no/api/groups.php')
|
||||
res = r.json()
|
||||
if 'results' not in res:
|
||||
self.stderr.write('Error: No groups, bailing...')
|
||||
|
||||
groups = res['results']
|
||||
for g in groups:
|
||||
if g['posix_group'] != "":
|
||||
obj, created = Group.objects.get_or_create(name=g['posix_group'])
|
||||
ou_obj, ou_created = OrgUnit.objects.get_or_create(name=g['division_name'], inside_id=g['division_id'])
|
||||
if 'styret' in g['posix_group']:
|
||||
ou_obj.admin_groups.add(obj)
|
||||
else:
|
||||
ou_obj.member_groups.add(obj)
|
||||
|
||||
self.stdout.write('Done.')
|
||||
@@ -1,30 +1,39 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('auth', '0001_initial'),
|
||||
("auth", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='OrgUnit',
|
||||
name="OrgUnit",
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('name', models.CharField(max_length=500)),
|
||||
('prefix', models.CharField(max_length=256, null=True, blank=True)),
|
||||
('posix_group', models.CharField(max_length=256, null=True, blank=True)),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('created', models.DateTimeField(auto_now_add=True)),
|
||||
('update', models.DateTimeField(auto_now=True)),
|
||||
('groups', models.ManyToManyField(to='auth.Group', null=True, blank=True)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
verbose_name="ID",
|
||||
serialize=False,
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=500)),
|
||||
("prefix", models.CharField(max_length=256, null=True, blank=True)),
|
||||
(
|
||||
"posix_group",
|
||||
models.CharField(max_length=256, null=True, blank=True),
|
||||
),
|
||||
("is_active", models.BooleanField(default=True)),
|
||||
("created", models.DateTimeField(auto_now_add=True)),
|
||||
("update", models.DateTimeField(auto_now=True)),
|
||||
(
|
||||
"groups",
|
||||
models.ManyToManyField(to="auth.Group", null=True, blank=True),
|
||||
),
|
||||
],
|
||||
options={
|
||||
},
|
||||
options={},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
]
|
||||
@@ -1,19 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mailadmin', '0001_initial'),
|
||||
("mailadmin", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='orgunit',
|
||||
name='inside_id',
|
||||
model_name="orgunit",
|
||||
name="inside_id",
|
||||
field=models.IntegerField(null=True, blank=True),
|
||||
preserve_default=True,
|
||||
),
|
||||
|
||||
@@ -1,39 +1,35 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('auth', '0001_initial'),
|
||||
('mailadmin', '0002_orgunit_inside_id'),
|
||||
("auth", "0001_initial"),
|
||||
("mailadmin", "0002_orgunit_inside_id"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='orgunit',
|
||||
options={'ordering': ['name']},
|
||||
name="orgunit",
|
||||
options={"ordering": ["name"]},
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='orgunit',
|
||||
name='groups',
|
||||
model_name="orgunit",
|
||||
name="groups",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='orgunit',
|
||||
name='posix_group',
|
||||
model_name="orgunit",
|
||||
name="posix_group",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='orgunit',
|
||||
name='admin_groups',
|
||||
field=models.ManyToManyField(related_name='admin_orgunits', null=True, to='auth.Group', blank=True),
|
||||
model_name="orgunit",
|
||||
name="admin_groups",
|
||||
field=models.ManyToManyField(related_name="admin_orgunits", null=True, to="auth.Group", blank=True),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='orgunit',
|
||||
name='member_groups',
|
||||
field=models.ManyToManyField(related_name='member_orgunits', null=True, to='auth.Group', blank=True),
|
||||
model_name="orgunit",
|
||||
name="member_groups",
|
||||
field=models.ManyToManyField(related_name="member_orgunits", null=True, to="auth.Group", blank=True),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
||||
@@ -1,8 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def normalize_prefixes(apps, schema_editor):
|
||||
@@ -19,26 +16,39 @@ def noop(*args, **kwargs):
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mailadmin', '0003_auto_20141105_2153'),
|
||||
("mailadmin", "0003_auto_20141105_2153"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Prefix',
|
||||
name="Prefix",
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('name', models.CharField(max_length=500, null=True, blank=True)),
|
||||
('orgunit', models.ForeignKey(related_name='prefixes', to='mailadmin.OrgUnit', on_delete=django.db.models.deletion.CASCADE)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
verbose_name="ID",
|
||||
serialize=False,
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=500, null=True, blank=True)),
|
||||
(
|
||||
"orgunit",
|
||||
models.ForeignKey(
|
||||
related_name="prefixes",
|
||||
to="mailadmin.OrgUnit",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
},
|
||||
options={},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.RunPython(normalize_prefixes, noop),
|
||||
migrations.RemoveField(
|
||||
model_name='orgunit',
|
||||
name='prefix',
|
||||
model_name="orgunit",
|
||||
name="prefix",
|
||||
),
|
||||
]
|
||||
@@ -1,18 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mailadmin', '0004_auto_20150125_1401'),
|
||||
("mailadmin", "0004_auto_20150125_1401"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='prefix',
|
||||
options={'verbose_name_plural': 'prefixes'},
|
||||
name="prefix",
|
||||
options={"verbose_name_plural": "prefixes"},
|
||||
),
|
||||
]
|
||||
@@ -1,25 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.1 on 2016-01-10 15:13
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mailadmin', '0005_auto_20150125_1940'),
|
||||
("mailadmin", "0005_auto_20150125_1940"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='orgunit',
|
||||
name='admin_groups',
|
||||
field=models.ManyToManyField(blank=True, related_name='admin_orgunits', to='auth.Group'),
|
||||
model_name="orgunit",
|
||||
name="admin_groups",
|
||||
field=models.ManyToManyField(blank=True, related_name="admin_orgunits", to="auth.Group"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='orgunit',
|
||||
name='member_groups',
|
||||
field=models.ManyToManyField(blank=True, related_name='member_orgunits', to='auth.Group'),
|
||||
model_name="orgunit",
|
||||
name="member_groups",
|
||||
field=models.ManyToManyField(blank=True, related_name="member_orgunits", to="auth.Group"),
|
||||
),
|
||||
]
|
||||
+8
-9
@@ -8,31 +8,30 @@ class OrgUnit(models.Model):
|
||||
|
||||
name = models.CharField(max_length=500)
|
||||
is_active = models.BooleanField(default=True)
|
||||
admin_groups = models.ManyToManyField(Group, blank=True, related_name='admin_orgunits')
|
||||
member_groups = models.ManyToManyField(Group, blank=True, related_name='member_orgunits')
|
||||
admin_groups = models.ManyToManyField(Group, blank=True, related_name="admin_orgunits")
|
||||
member_groups = models.ManyToManyField(Group, blank=True, related_name="member_orgunits")
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
update = models.DateTimeField(auto_now=True)
|
||||
inside_id = models.IntegerField(null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
ordering = ["name"]
|
||||
|
||||
|
||||
class PrefixQueryset(models.QuerySet):
|
||||
|
||||
def as_regex(self):
|
||||
prefixes = self.values_list('name', flat=True)
|
||||
return '|'.join(['^{0}-|^{0}@'.format(p) for p in prefixes])
|
||||
prefixes = self.values_list("name", flat=True)
|
||||
return "|".join([f"^{p}-|^{p}@" for p in prefixes])
|
||||
|
||||
|
||||
class Prefix(models.Model):
|
||||
def __str__(self):
|
||||
return self.name or ''
|
||||
return self.name or ""
|
||||
|
||||
name = models.CharField(max_length=500, blank=True, null=True)
|
||||
orgunit = models.ForeignKey('mailadmin.OrgUnit', models.CASCADE, related_name='prefixes')
|
||||
orgunit = models.ForeignKey("mailadmin.OrgUnit", models.CASCADE, related_name="prefixes")
|
||||
|
||||
objects = PrefixQueryset.as_manager()
|
||||
|
||||
class Meta:
|
||||
verbose_name_plural = 'prefixes'
|
||||
verbose_name_plural = "prefixes"
|
||||
@@ -1,17 +1,21 @@
|
||||
from rest_framework import permissions
|
||||
import re
|
||||
|
||||
from rest_framework import permissions
|
||||
|
||||
from mailadmin.serializers import AliasSerializer
|
||||
|
||||
|
||||
class SourcePrefixOwner(permissions.BasePermission):
|
||||
""" Ref: http://www.django-rest-framework.org/api-guide/permissions """
|
||||
"""Ref: http://www.django-rest-framework.org/api-guide/permissions"""
|
||||
|
||||
def has_permission(self, request, view):
|
||||
assert hasattr(view, 'user_prefixes')
|
||||
assert hasattr(view, "user_prefixes")
|
||||
|
||||
# Read permissions (GET, HEAD or OPTIONS) are allowed for all authenticated request
|
||||
if request.method in permissions.SAFE_METHODS or request.method not in ['DELETE', 'POST']:
|
||||
if request.method in permissions.SAFE_METHODS or request.method not in [
|
||||
"DELETE",
|
||||
"POST",
|
||||
]:
|
||||
return True
|
||||
|
||||
if request.user.is_superuser:
|
||||
@@ -29,8 +33,4 @@ class SourcePrefixOwner(permissions.BasePermission):
|
||||
# Prepare regular expression
|
||||
regexp = my_prefixes.as_regex()
|
||||
# All source adresses should match the prefix regexp
|
||||
for alias in serializer.initial_data:
|
||||
if re.search(regexp, alias.get('source', '')) is None:
|
||||
return False
|
||||
|
||||
return True
|
||||
return all(re.search(regexp, alias.get("source", "")) is not None for alias in serializer.initial_data)
|
||||
+18
-12
@@ -1,15 +1,15 @@
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User, Group
|
||||
from django.contrib.auth.models import Group, User
|
||||
from rest_framework import serializers
|
||||
from rest_framework.relations import SlugRelatedField
|
||||
|
||||
from mailadmin.models import OrgUnit
|
||||
from rest_framework.relations import SlugRelatedField
|
||||
|
||||
|
||||
class GroupSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Group
|
||||
fields = ['id', 'name']
|
||||
fields = ["id", "name"]
|
||||
|
||||
|
||||
class UserSerializer(serializers.ModelSerializer):
|
||||
@@ -17,21 +17,27 @@ class UserSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
exclude = ['password', 'user_permissions']
|
||||
exclude = ["password", "user_permissions"]
|
||||
|
||||
|
||||
class OrgUnitSerializer(serializers.ModelSerializer):
|
||||
admin_groups = GroupSerializer(many=True)
|
||||
member_groups = GroupSerializer(many=True)
|
||||
prefixes = SlugRelatedField(
|
||||
many=True,
|
||||
read_only=True,
|
||||
slug_field='name')
|
||||
prefixes = SlugRelatedField(many=True, read_only=True, slug_field="name")
|
||||
|
||||
class Meta:
|
||||
model = OrgUnit
|
||||
fields = ['id', 'admin_groups', 'member_groups', 'prefixes', 'inside_id', 'name', 'is_active',
|
||||
'created', 'update']
|
||||
fields = [
|
||||
"id",
|
||||
"admin_groups",
|
||||
"member_groups",
|
||||
"prefixes",
|
||||
"inside_id",
|
||||
"name",
|
||||
"is_active",
|
||||
"created",
|
||||
"update",
|
||||
]
|
||||
|
||||
|
||||
class AliasSerializer(serializers.Serializer):
|
||||
@@ -42,8 +48,8 @@ class AliasSerializer(serializers.Serializer):
|
||||
def validate_source(self, value):
|
||||
_domain_name = settings.NEUF_EMAIL_DOMAIN_NAME
|
||||
|
||||
domain_part = value.split('@')[1]
|
||||
domain_part = value.split("@")[1]
|
||||
if domain_part != _domain_name:
|
||||
raise serializers.ValidationError("source '{0}' is not in domain {1}".format(value, _domain_name))
|
||||
raise serializers.ValidationError(f"source '{value}' is not in domain {_domain_name}")
|
||||
|
||||
return value
|
||||
@@ -0,0 +1,16 @@
|
||||
from http import HTTPStatus
|
||||
|
||||
import pytest
|
||||
from django.contrib.auth.models import User
|
||||
from django.urls import reverse
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def user(db):
|
||||
return User.objects.create(username="admin")
|
||||
|
||||
|
||||
def test_open_lists(user, client):
|
||||
client.force_login(user=user)
|
||||
response = client.get(reverse("lists"))
|
||||
assert response.status_code == HTTPStatus.OK
|
||||
@@ -1,15 +0,0 @@
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import TestCase
|
||||
# import unittest
|
||||
from django.urls import reverse
|
||||
|
||||
|
||||
class FrontendTest(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.user = User.objects.create(username='admin')
|
||||
self.client.force_login(user=self.user)
|
||||
|
||||
def test_open_lists(self):
|
||||
response = self.client.get(reverse('lists'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
+19
-11
@@ -1,23 +1,31 @@
|
||||
from django.urls import path
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
from mailadmin.views import MyUserViewSet, MyOrgUnitViewSet, OrgUnitViewSet, AliasesView, get_domains, index, lists, \
|
||||
AliasesDeleteView
|
||||
from mailadmin.views import (
|
||||
AliasesDeleteView,
|
||||
AliasesView,
|
||||
MyOrgUnitViewSet,
|
||||
MyUserViewSet,
|
||||
OrgUnitViewSet,
|
||||
get_domains,
|
||||
index,
|
||||
lists,
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
path('', index, name='index'),
|
||||
path('lists/', lists, name='lists'),
|
||||
path("", index, name="index"),
|
||||
path("lists/", lists, name="lists"),
|
||||
]
|
||||
|
||||
# API
|
||||
router = DefaultRouter()
|
||||
router.register(r'api/me', MyUserViewSet, basename='me')
|
||||
router.register(r'api/orgunits', MyOrgUnitViewSet, basename='ous')
|
||||
router.register(r'api/orgunits/all', OrgUnitViewSet, basename='allous')
|
||||
router.register(r"api/me", MyUserViewSet, basename="me")
|
||||
router.register(r"api/orgunits", MyOrgUnitViewSet, basename="ous")
|
||||
router.register(r"api/orgunits/all", OrgUnitViewSet, basename="allous")
|
||||
urlpatterns += router.urls
|
||||
|
||||
urlpatterns += [
|
||||
path('api/aliases/', AliasesView.as_view(), name='aliases'),
|
||||
path('api/aliases/delete/', AliasesDeleteView.as_view(), name='aliases-delete'),
|
||||
path('api/domains/', get_domains, name='email-domains'),
|
||||
]
|
||||
path("api/aliases/", AliasesView.as_view(), name="aliases"),
|
||||
path("api/aliases/delete/", AliasesDeleteView.as_view(), name="aliases-delete"),
|
||||
path("api/domains/", get_domains, name="email-domains"),
|
||||
]
|
||||
+25
-21
@@ -1,21 +1,21 @@
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import login, logout as auth_logout
|
||||
from django.contrib.auth import login
|
||||
from django.contrib.auth import logout as auth_logout
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.forms import AuthenticationForm
|
||||
from django.contrib.auth.models import User
|
||||
from django.shortcuts import render, redirect
|
||||
import logging
|
||||
from rest_framework import viewsets, views
|
||||
from django.shortcuts import redirect, render
|
||||
from rest_framework import status, views, viewsets
|
||||
from rest_framework.decorators import api_view
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
|
||||
from mailadmin.api import DjangoPostfixDovecotAPI
|
||||
from mailadmin.models import OrgUnit, Prefix
|
||||
from mailadmin.serializers import UserSerializer, OrgUnitSerializer, AliasSerializer
|
||||
from mailadmin.permissions import SourcePrefixOwner
|
||||
|
||||
from mailadmin.serializers import AliasSerializer, OrgUnitSerializer, UserSerializer
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -26,14 +26,13 @@ def index(request):
|
||||
if form.is_valid():
|
||||
# Log the user in.
|
||||
login(request, form.get_user())
|
||||
return redirect('lists')
|
||||
return redirect("lists")
|
||||
elif request.user.is_authenticated:
|
||||
return redirect("lists")
|
||||
else:
|
||||
if request.user.is_authenticated:
|
||||
return redirect('lists')
|
||||
else:
|
||||
form = AuthenticationForm(request)
|
||||
form = AuthenticationForm(request)
|
||||
|
||||
return render(request, "index.html", {'form': form})
|
||||
return render(request, "index.html", {"form": form})
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -43,19 +42,22 @@ def lists(request):
|
||||
|
||||
def logout(request):
|
||||
auth_logout(request)
|
||||
return redirect('index')
|
||||
return redirect("index")
|
||||
|
||||
|
||||
class BaseAliasesView(views.APIView):
|
||||
_api = DjangoPostfixDovecotAPI()
|
||||
permission_classes = (IsAuthenticated, SourcePrefixOwner,)
|
||||
permission_classes = (
|
||||
IsAuthenticated,
|
||||
SourcePrefixOwner,
|
||||
)
|
||||
|
||||
def _user_groups(self, request):
|
||||
group_filter = request.query_params.get('groups', None)
|
||||
group_filter = request.query_params.get("groups", None)
|
||||
if group_filter is None:
|
||||
user_groups = request.user.groups.all()
|
||||
else:
|
||||
group_filter = group_filter.split(',') if ',' in group_filter else [group_filter]
|
||||
group_filter = group_filter.split(",") if "," in group_filter else [group_filter]
|
||||
user_groups = request.user.groups.filter(pk__in=group_filter)
|
||||
|
||||
return user_groups
|
||||
@@ -71,14 +73,15 @@ class BaseAliasesView(views.APIView):
|
||||
|
||||
|
||||
class AliasesView(BaseAliasesView):
|
||||
""" List and create aliases from django-postfix-dovecot-api """
|
||||
"""List and create aliases from django-postfix-dovecot-api"""
|
||||
|
||||
def get(self, request):
|
||||
user_prefixes = self.user_prefixes(request)
|
||||
|
||||
# Prepare regular expression
|
||||
regex = user_prefixes.as_regex()
|
||||
if len(user_prefixes) == 0:
|
||||
return Response({'result': 'No results'}) # Need at least one
|
||||
return Response({"result": "No results"}) # Need at least one
|
||||
|
||||
return Response(self._api.list_aliases_regex(regex))
|
||||
|
||||
@@ -91,7 +94,8 @@ class AliasesView(BaseAliasesView):
|
||||
|
||||
|
||||
class AliasesDeleteView(BaseAliasesView):
|
||||
""" Delete aliases (using POST) from django-postfix-dovecot-api """
|
||||
"""Delete aliases (using POST) from django-postfix-dovecot-api"""
|
||||
|
||||
def post(self, request):
|
||||
aliases = AliasSerializer(data=request.data, many=True)
|
||||
if not aliases.is_valid():
|
||||
@@ -128,7 +132,7 @@ class OrgUnitViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
|
||||
@api_view(['GET'])
|
||||
@api_view(["GET"])
|
||||
def get_domains(request):
|
||||
api = DjangoPostfixDovecotAPI()
|
||||
domains = api.list_domains(name=settings.NEUF_EMAIL_DOMAIN_NAME)
|
||||
|
||||
+56
-63
@@ -1,4 +1,6 @@
|
||||
import contextlib
|
||||
from pathlib import Path
|
||||
|
||||
import ldap
|
||||
from django_auth_ldap.config import LDAPSearch, PosixGroupType
|
||||
|
||||
@@ -9,7 +11,7 @@ BASE_DIR = Path(__file__).parent.parent
|
||||
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = 'g658807^w2!kzsdb_wznxo$5xpjb2x(sfoub%l_ap77@@_zt&*'
|
||||
SECRET_KEY = "g658807^w2!kzsdb_wznxo$5xpjb2x(sfoub%l_ap77@@_zt&*" # noqa: S105
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
@@ -18,56 +20,55 @@ ALLOWED_HOSTS = []
|
||||
|
||||
# Application definition
|
||||
INSTALLED_APPS = (
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
|
||||
'rest_framework',
|
||||
'bootstrap4',
|
||||
'mailadmin',
|
||||
"django.contrib.admin",
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
"rest_framework",
|
||||
"bootstrap4",
|
||||
"mailadmin",
|
||||
)
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'mailadminsite.urls'
|
||||
ROOT_URLCONF = "mailadminsite.urls"
|
||||
|
||||
WSGI_APPLICATION = 'mailadminsite.wsgi.application'
|
||||
WSGI_APPLICATION = "mailadminsite.wsgi.application"
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': BASE_DIR.joinpath('db.sqlite3'),
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.sqlite3",
|
||||
"NAME": BASE_DIR.joinpath("db.sqlite3"),
|
||||
}
|
||||
}
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.i18n',
|
||||
'django.template.context_processors.media',
|
||||
'django.template.context_processors.static',
|
||||
'django.template.context_processors.tz',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'django.template.context_processors.request',
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"DIRS": [],
|
||||
"APP_DIRS": True,
|
||||
"OPTIONS": {
|
||||
"context_processors": [
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.template.context_processors.debug",
|
||||
"django.template.context_processors.i18n",
|
||||
"django.template.context_processors.media",
|
||||
"django.template.context_processors.static",
|
||||
"django.template.context_processors.tz",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
"django.template.context_processors.request",
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -75,35 +76,35 @@ TEMPLATES = [
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/1.7/topics/i18n/
|
||||
LANGUAGE_CODE = 'nb'
|
||||
TIME_ZONE = 'Europe/Oslo'
|
||||
LANGUAGE_CODE = "nb"
|
||||
TIME_ZONE = "Europe/Oslo"
|
||||
USE_I18N = True
|
||||
USE_TZ = True
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/1.7/howto/static-files/
|
||||
STATIC_URL = '/static/'
|
||||
STATIC_ROOT = BASE_DIR.joinpath('staticfiles')
|
||||
STATIC_URL = "/static/"
|
||||
STATIC_ROOT = BASE_DIR.joinpath("staticfiles")
|
||||
|
||||
LOGIN_REDIRECT_URL = '/lists/'
|
||||
LOGIN_REDIRECT_URL = "/lists/"
|
||||
|
||||
# Django Postfix Dovecot
|
||||
DPD_API_URL = 'http://localhost:8080/api'
|
||||
DPD_API_URL = "http://localhost:8080/api"
|
||||
DPD_API_USERNAME = ""
|
||||
DPD_API_PASSWORD = ""
|
||||
|
||||
NEUF_EMAIL_DOMAIN_NAME = 'stry.no'
|
||||
NEUF_EMAIL_DOMAIN_NAME = "stry.no"
|
||||
|
||||
AUTHENTICATION_BACKENDS = (
|
||||
'mailadmin.backends.LDAPEmailBackend',
|
||||
'mailadmin.backends.LDAPUsernameBackend',
|
||||
'django.contrib.auth.backends.ModelBackend',
|
||||
"mailadmin.backends.LDAPEmailBackend",
|
||||
"mailadmin.backends.LDAPUsernameBackend",
|
||||
"django.contrib.auth.backends.ModelBackend",
|
||||
)
|
||||
|
||||
# LDAP server URI and BIND_DN, same as db-settings
|
||||
AUTH_LDAP_U_SERVER_URI = 'ldap://localhost/'
|
||||
AUTH_LDAP_U_BIND_DN = 'cn=admin,dc=neuf,dc=no'
|
||||
AUTH_LDAP_U_BIND_PASSWORD = 'toor'
|
||||
AUTH_LDAP_U_SERVER_URI = "ldap://localhost/"
|
||||
AUTH_LDAP_U_BIND_DN = "cn=admin,dc=neuf,dc=no"
|
||||
AUTH_LDAP_U_BIND_PASSWORD = "toor" # noqa: S105
|
||||
|
||||
# Basic user auth
|
||||
AUTH_LDAP_U_USER_SEARCH = LDAPSearch("ou=People,dc=neuf,dc=no", ldap.SCOPE_ONELEVEL, "(uid=%(user)s)")
|
||||
@@ -116,7 +117,7 @@ AUTH_LDAP_U_MIRROR_GROUPS = True
|
||||
AUTH_LDAP_U_USER_FLAGS_BY_GROUP = {
|
||||
"is_active": "cn=dns-aktiv,ou=Groups,dc=neuf,dc=no",
|
||||
"is_staff": "cn=edb,ou=Groups,dc=neuf,dc=no",
|
||||
"is_superuser": "cn=edbadmin,ou=Groups,dc=neuf,dc=no"
|
||||
"is_superuser": "cn=edbadmin,ou=Groups,dc=neuf,dc=no",
|
||||
}
|
||||
# User attribute mappings
|
||||
AUTH_LDAP_U_USER_ATTR_MAP = {
|
||||
@@ -125,9 +126,7 @@ AUTH_LDAP_U_USER_ATTR_MAP = {
|
||||
"email": "mail",
|
||||
}
|
||||
# User profile attribute mappings
|
||||
AUTH_LDAP_U_PROFILE_ATTR_MAP = {
|
||||
"home_directory": "homeDirectory"
|
||||
}
|
||||
AUTH_LDAP_U_PROFILE_ATTR_MAP = {"home_directory": "homeDirectory"}
|
||||
# Allways update the django user object on authentication.
|
||||
AUTH_LDAP_U_ALWAYS_UPDATE_USER = True
|
||||
|
||||
@@ -146,16 +145,10 @@ AUTH_LDAP_E_USER_ATTR_MAP = AUTH_LDAP_U_USER_ATTR_MAP
|
||||
AUTH_LDAP_E_PROFILE_ATTR_MAP = AUTH_LDAP_U_PROFILE_ATTR_MAP
|
||||
AUTH_LDAP_E_ALWAYS_UPDATE_USER = AUTH_LDAP_U_ALWAYS_UPDATE_USER
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||
'rest_framework.authentication.SessionAuthentication',
|
||||
)
|
||||
}
|
||||
REST_FRAMEWORK = {"DEFAULT_AUTHENTICATION_CLASSES": ("rest_framework.authentication.SessionAuthentication",)}
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||
|
||||
# Local settings
|
||||
try:
|
||||
from .local_settings import *
|
||||
except ImportError:
|
||||
pass
|
||||
with contextlib.suppress(ImportError):
|
||||
from .local_settings import * # noqa: F403
|
||||
@@ -1,18 +1,18 @@
|
||||
from django.urls import include, path, re_path
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.views import LoginView
|
||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||
from django.urls import include, path, re_path
|
||||
from rest_framework import urls as rest_framework_urls
|
||||
|
||||
from mailadmin import urls as mailadmin_urls
|
||||
from mailadmin.views import logout
|
||||
|
||||
urlpatterns = [
|
||||
path('', include(mailadmin_urls)),
|
||||
re_path(r'^admin/', admin.site.urls),
|
||||
path('api-auth/', include(rest_framework_urls, namespace='rest_framework')),
|
||||
path('accounts/login/', LoginView.as_view(), name='login'),
|
||||
path('accounts/logout/', logout, name='logout')
|
||||
path("", include(mailadmin_urls)),
|
||||
re_path(r"^admin/", admin.site.urls),
|
||||
path("api-auth/", include(rest_framework_urls, namespace="rest_framework")),
|
||||
path("accounts/login/", LoginView.as_view(), name="login"),
|
||||
path("accounts/logout/", logout, name="logout"),
|
||||
]
|
||||
|
||||
urlpatterns += staticfiles_urlpatterns()
|
||||
@@ -8,7 +8,9 @@ https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mailadminsite.settings")
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
application = get_wsgi_application()
|
||||
@@ -17,3 +17,36 @@ dependencies = [
|
||||
"requests==2.*",
|
||||
"sentry-sdk==1.*",
|
||||
]
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"pytest>=8.3.5",
|
||||
"pytest-django>=4.11.1",
|
||||
"ruff>=0.11.12",
|
||||
]
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 120
|
||||
target-version = "py310"
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = ["ALL"]
|
||||
ignore = [
|
||||
"D", # pydocstyle: Allow undocumented code
|
||||
"ANN", # flake8-annotations: Allow untyped code
|
||||
"TD", "FIX", # Allow TODOs and FIXMEs
|
||||
"FBT002", # Allow boolean args
|
||||
"G004", # Allow logging using f-string
|
||||
"EM101", "EM102", # Allow string in exceptions (prioritice code readability vs exception time readability)
|
||||
"TRY003", # Allow long exception messages
|
||||
"E501", # Allow long lines
|
||||
"COM812", "ISC001", # Avoid conflicting with ruff formatter
|
||||
"RUF012" # FIXME: Fixy fix
|
||||
]
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
# Testing code can deviate a bit more
|
||||
"**/tests.py" = ["S101", "S106", "PLR2004"]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
DJANGO_SETTINGS_MODULE = "mailadminsite.settings"
|
||||
@@ -127,6 +127,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "45.0.3"
|
||||
@@ -295,6 +304,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iniconfig"
|
||||
version = "2.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "invoke"
|
||||
version = "2.2.0"
|
||||
@@ -323,6 +341,13 @@ dependencies = [
|
||||
{ name = "sentry-sdk" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-django" },
|
||||
{ name = "ruff" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "django", specifier = "==4.2.*" },
|
||||
@@ -339,6 +364,13 @@ requires-dist = [
|
||||
{ name = "sentry-sdk", specifier = "==1.*" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "pytest", specifier = ">=8.3.5" },
|
||||
{ name = "pytest-django", specifier = ">=4.11.1" },
|
||||
{ name = "ruff", specifier = ">=0.11.12" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markdown"
|
||||
version = "3.8"
|
||||
@@ -357,6 +389,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/29/01/e80141f1cd0459e4c9a5dd309dee135bbae41d6c6c121252fdd853001a8a/mysqlclient-2.2.7-cp313-cp313-win_amd64.whl", hash = "sha256:201a6faa301011dd07bca6b651fe5aaa546d7c9a5426835a06c3172e1056a3c5", size = 208000, upload-time = "2025-01-10T11:56:32.293Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "25.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paramiko"
|
||||
version = "3.5.1"
|
||||
@@ -371,6 +412,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/15/f8/c7bd0ef12954a81a1d3cea60a13946bd9a49a0036a5927770c461eade7ae/paramiko-3.5.1-py3-none-any.whl", hash = "sha256:43b9a0501fc2b5e70680388d9346cf252cfb7d00b0667c39e80eb43a408b8f61", size = 227298, upload-time = "2025-02-04T02:37:57.672Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.6.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyasn1"
|
||||
version = "0.6.1"
|
||||
@@ -421,6 +471,33 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/22/d3db169895faaf3e2eda892f005f433a62db2decbcfbc2f61e6517adfa87/PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93", size = 212141, upload-time = "2022-01-07T22:06:01.861Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "8.3.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "iniconfig" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pluggy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-django"
|
||||
version = "4.11.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pytest" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b1/fb/55d580352db26eb3d59ad50c64321ddfe228d3d8ac107db05387a2fadf3a/pytest_django-4.11.1.tar.gz", hash = "sha256:a949141a1ee103cb0e7a20f1451d355f83f5e4a5d07bdd4dcfdd1fd0ff227991", size = 86202, upload-time = "2025-04-03T18:56:09.338Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/be/ac/bd0608d229ec808e51a21044f3f2f27b9a37e7a0ebaca7247882e67876af/pytest_django-4.11.1-py3-none-any.whl", hash = "sha256:1b63773f648aa3d8541000c26929c1ea63934be1cfa674c76436966d73fe6a10", size = 25281, upload-time = "2025-04-03T18:56:07.678Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-ldap"
|
||||
version = "3.4.4"
|
||||
@@ -446,6 +523,31 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928, upload-time = "2024-05-29T15:37:47.027Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.11.12"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/15/0a/92416b159ec00cdf11e5882a9d80d29bf84bba3dbebc51c4898bfbca1da6/ruff-0.11.12.tar.gz", hash = "sha256:43cf7f69c7d7c7d7513b9d59c5d8cafd704e05944f978614aa9faff6ac202603", size = 4202289, upload-time = "2025-05-29T13:31:40.037Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/60/cc/53eb79f012d15e136d40a8e8fc519ba8f55a057f60b29c2df34efd47c6e3/ruff-0.11.12-py3-none-linux_armv6l.whl", hash = "sha256:c7680aa2f0d4c4f43353d1e72123955c7a2159b8646cd43402de6d4a3a25d7cc", size = 10285597, upload-time = "2025-05-29T13:30:57.539Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/d7/73386e9fb0232b015a23f62fea7503f96e29c29e6c45461d4a73bac74df9/ruff-0.11.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:2cad64843da9f134565c20bcc430642de897b8ea02e2e79e6e02a76b8dcad7c3", size = 11053154, upload-time = "2025-05-29T13:31:00.865Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/eb/3eae144c5114e92deb65a0cb2c72326c8469e14991e9bc3ec0349da1331c/ruff-0.11.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:9b6886b524a1c659cee1758140138455d3c029783d1b9e643f3624a5ee0cb0aa", size = 10403048, upload-time = "2025-05-29T13:31:03.413Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/64/20c54b20e58b1058db6689e94731f2a22e9f7abab74e1a758dfba058b6ca/ruff-0.11.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc3a3690aad6e86c1958d3ec3c38c4594b6ecec75c1f531e84160bd827b2012", size = 10597062, upload-time = "2025-05-29T13:31:05.539Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/3a/79fa6a9a39422a400564ca7233a689a151f1039110f0bbbabcb38106883a/ruff-0.11.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f97fdbc2549f456c65b3b0048560d44ddd540db1f27c778a938371424b49fe4a", size = 10155152, upload-time = "2025-05-29T13:31:07.986Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/a4/22c2c97b2340aa968af3a39bc38045e78d36abd4ed3fa2bde91c31e712e3/ruff-0.11.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74adf84960236961090e2d1348c1a67d940fd12e811a33fb3d107df61eef8fc7", size = 11723067, upload-time = "2025-05-29T13:31:10.57Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/cf/3e452fbd9597bcd8058856ecd42b22751749d07935793a1856d988154151/ruff-0.11.12-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b56697e5b8bcf1d61293ccfe63873aba08fdbcbbba839fc046ec5926bdb25a3a", size = 12460807, upload-time = "2025-05-29T13:31:12.88Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/ec/8f170381a15e1eb7d93cb4feef8d17334d5a1eb33fee273aee5d1f8241a3/ruff-0.11.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d47afa45e7b0eaf5e5969c6b39cbd108be83910b5c74626247e366fd7a36a13", size = 12063261, upload-time = "2025-05-29T13:31:15.236Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/bf/57208f8c0a8153a14652a85f4116c0002148e83770d7a41f2e90b52d2b4e/ruff-0.11.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bf9603fe1bf949de8b09a2da896f05c01ed7a187f4a386cdba6760e7f61be", size = 11329601, upload-time = "2025-05-29T13:31:18.68Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/56/edf942f7fdac5888094d9ffa303f12096f1a93eb46570bcf5f14c0c70880/ruff-0.11.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08033320e979df3b20dba567c62f69c45e01df708b0f9c83912d7abd3e0801cd", size = 11522186, upload-time = "2025-05-29T13:31:21.216Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/63/79ffef65246911ed7e2290aeece48739d9603b3a35f9529fec0fc6c26400/ruff-0.11.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:929b7706584f5bfd61d67d5070f399057d07c70585fa8c4491d78ada452d3bef", size = 10449032, upload-time = "2025-05-29T13:31:23.417Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/19/8c9d4d8a1c2a3f5a1ea45a64b42593d50e28b8e038f1aafd65d6b43647f3/ruff-0.11.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7de4a73205dc5756b8e09ee3ed67c38312dce1aa28972b93150f5751199981b5", size = 10129370, upload-time = "2025-05-29T13:31:25.777Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/0f/2d15533eaa18f460530a857e1778900cd867ded67f16c85723569d54e410/ruff-0.11.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2635c2a90ac1b8ca9e93b70af59dfd1dd2026a40e2d6eebaa3efb0465dd9cf02", size = 11123529, upload-time = "2025-05-29T13:31:28.396Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/e2/4c2ac669534bdded835356813f48ea33cfb3a947dc47f270038364587088/ruff-0.11.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d05d6a78a89166f03f03a198ecc9d18779076ad0eec476819467acb401028c0c", size = 11577642, upload-time = "2025-05-29T13:31:30.647Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/9b/c9ddf7f924d5617a1c94a93ba595f4b24cb5bc50e98b94433ab3f7ad27e5/ruff-0.11.12-py3-none-win32.whl", hash = "sha256:f5a07f49767c4be4772d161bfc049c1f242db0cfe1bd976e0f0886732a4765d6", size = 10475511, upload-time = "2025-05-29T13:31:32.917Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/d6/74fb6d3470c1aada019ffff33c0f9210af746cca0a4de19a1f10ce54968a/ruff-0.11.12-py3-none-win_amd64.whl", hash = "sha256:5a4d9f8030d8c3a45df201d7fb3ed38d0219bccd7955268e863ee4a115fa0832", size = 11523573, upload-time = "2025-05-29T13:31:35.782Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/42/d58086ec20f52d2b0140752ae54b355ea2be2ed46f914231136dd1effcc7/ruff-0.11.12-py3-none-win_arm64.whl", hash = "sha256:65194e37853158d368e333ba282217941029a28ea90913c67e558c611d04daa5", size = 10697770, upload-time = "2025-05-29T13:31:38.009Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sentry-sdk"
|
||||
version = "1.45.1"
|
||||
|
||||
Reference in new issue
Block a user