2 Commits
Author SHA1 Message Date
nikolark 419857f195 oops 2022-08-07 15:48:13 +02:00
nikolark b12413ed67 ✨ Modernize bit bit
- bumps
- poetry
- linting
2022-08-07 15:44:40 +02:00
31 changed files with 1408 additions and 332 deletions

No files matched your search

+1
View File
@@ -2,5 +2,6 @@
*.pyc
.idea
venv
dpdapi/local.py
local_settings.py
db.sqlite3
+38 -15
View File
@@ -1,19 +1,42 @@
image: python:latest
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/pip-cache"
cache:
default:
image: python:3.8-slim
cache:
paths:
- "$CI_PROJECT_DIR/pip-cache"
key: "$CI_PROJECT_ID"
before_script:
- pip install -r requirements.txt
- pip install coverage
- .cache/pip
- .venv
test:
before_script:
- poetry install
script:
- coverage run --source=dpdapi --omit=dpdapi/migrations/* manage.py test -v2
- coverage report
coverage: '/TOTAL.+ ([0-9]{1,3}%)/'
- bin/ensure-python-deps.sh
- poetry run python manage.py migrate
- poetry run python manage.py check
- poetry run coverage run --source=dpdapi --omit=dpdapi/migrations/* -m pytest -v
- poetry run coverage report
- poetry run coverage xml
- poetry run bin/lint
coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
deploy:
stage: deploy
before_script:
# Install ssh if not already
- 'command -v ssh-agent >/dev/null || ( apt-get update -y && apt-get install openssh-client -y )'
- eval $(ssh-agent -s)
# Make SSH key available
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
# Add known host keys
- echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts
script:
- ssh gitdeploy@lynx.neuf.no "$(< bin/deploy)"
only:
- main
+36
View File
@@ -0,0 +1,36 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: local
hooks:
- id: black
name: black
entry: black
args: [--check]
language: system
require_serial: true
types: [python]
- id: isort
name: isort
entry: isort
args: [--check]
language: system
types: [python]
- id: pycln
name: pycln
entry: pycln
args: [--check]
language: system
types: [python]
- id: pyupgrade
name: pyupgrade
entry: pyupgrade
args: [--py38-plus]
language: system
types: [python]
- id: django-upgrade
name: django-upgrade
entry: django-upgrade
language: system
types: [python]
args: [--target-version, "3.2"]
+13 -7
View File
@@ -1,14 +1,20 @@
# Install
apt install python-dev libmysqlclient-dev python-virtualenv
python -m venv venv
. venv/bin/activate
pip install -r requirements.txt
# django-postfix-dovecot-api
## Getting started
```bash
sudo apt install libmysqlclient-dev
poetry install
poetry run bin/run
```
## Deploy
# Deploy
First you need to have your public key in `/home/gitdeploy/.ssh/authorized_keys` on the server and then you can do:
fab deploy
# Inspiration:
## Inspiration:
-
- http://modoboa.readthedocs.org/
- https://github.com/lgunsch/django-vmail
Executable
+18
View File
@@ -0,0 +1,18 @@
#!/bin/bash
set -eu
export DJANGO_SETTINGS_MODULE=dusken.settings.prod
PROJECT_PATH=/var/www/neuf.no/django-postfix-dovecot-api
PROCESS_NAME="mxapi.neuf.no"
echo "Deploying mxapi to ${PROJECT_PATH}"
cd "$PROJECT_PATH" || exit 1
git pull
poetry install
umask 022; poetry run python manage.py collectstatic --noinput -c # Collect static
poetry run python manage.py migrate # Run DB migrations
# Reload backend
echo "Reloading supervisor process ${PROCESS_NAME}"
sudo /usr/bin/supervisorctl pid "$PROCESS_NAME" | xargs kill -HUP
+6
View File
@@ -0,0 +1,6 @@
#!/bin/bash
set -eu
poetry config virtualenvs.in-project true
poetry run pip install -U pip wheel
poetry install
Executable
+9
View File
@@ -0,0 +1,9 @@
#!/bin/bash
SRC_DIR="dpdapi"
black $SRC_DIR
isort $SRC_DIR
pycln $SRC_DIR
pyupgrade --py38-plus $(find $SRC_DIR -name '*.py')
django-upgrade --target-version 3.2 $(find $SRC_DIR -name '*.py')
Executable
+4
View File
@@ -0,0 +1,4 @@
#!/usr/bin/env bash
set -eu
pre-commit run -a
Executable
+3
View File
@@ -0,0 +1,3 @@
#!/bin/bash
python manage.py runserver
Executable
+3
View File
@@ -0,0 +1,3 @@
#!/bin/bash
pytest -v
+3 -3
View File
@@ -5,9 +5,9 @@ from dpdapi.models import Alias, Domain, User
@admin.register(Alias)
class AliasAdmin(admin.ModelAdmin):
list_display = ['source', 'destination', 'active', 'domain']
list_filter = ['domain']
search_fields = ['source', 'destination']
list_display = ["source", "destination", "active", "domain"]
list_filter = ["domain"]
search_fields = ["source", "destination"]
@admin.register(Domain)
+1 -4
View File
@@ -7,10 +7,7 @@ from dpdapi.models import Alias
class AliasRegexFilter(django_filters.FilterSet):
class Meta:
model = Alias
fields = {
'source': ['iregex', 'regex'],
'destination': ['iregex', 'regex']
}
fields = {"source": ["iregex", "regex"], "destination": ["iregex", "regex"]}
class AliasRegexFilterBackend(BaseFilterBackend):
+20 -18
View File
@@ -1,60 +1,62 @@
import os
from django.core.management.base import BaseCommand
from django.db.utils import IntegrityError
import os
from dpdapi.models import Alias, Domain
class Command(BaseCommand):
help = 'Import aliases for a single domain at a time, which is the domain of the first source address.'
help = "Import aliases for a single domain at a time, which is the domain of the first source address."
def handle(self, *args, **options):
if len(args) == 1:
if os.path.exists(args[0]):
self.import_file(args[0])
else:
self.stdout.write("{} does not exist".format(args[0]))
self.stdout.write(f"{args[0]} does not exist")
else:
self.stdout.write("Missing argument")
def import_file(self, filename):
new_aliases = []
counts = {'imported': 0, 'existing': 0}
counts = {"imported": 0, "existing": 0}
# Open file
with open(filename) as f:
# Parse
for l in f:
if l.startswith('*: '):
if l.startswith("*: "):
continue # Skip catch all
source, destinations = l.strip().split(': ')
for destination in destinations.split(','):
if destination.startswith('"|') or '-admin@' in destination:
source, destinations = l.strip().split(": ")
for destination in destinations.split(","):
if destination.startswith('"|') or "-admin@" in destination:
continue # Skip mailman-aliases
new_aliases.append(Alias(source=source, destination=destination))
if len(new_aliases) == 0:
self.stdout.write('No aliases found')
self.stdout.write("No aliases found")
return
# Lookup domain
domain_name = new_aliases[0].source.split('@')[1] # Assumes that every alias is in the same domain
domain_name = new_aliases[0].source.split("@")[1] # Assumes that every alias is in the same domain
d, created = Domain.objects.get_or_create(name=domain_name)
if created:
self.stdout.write('Created domain {}.'.format(d.name))
self.stdout.write(f"Created domain {d.name}.")
# Try to create, log if exists. Slow but works
for a in new_aliases:
a.domain = d
try:
a.save()
counts['imported'] += 1 # Woop!
counts["imported"] += 1 # Woop!
except IntegrityError:
counts['existing'] += 1 # Exists
self.stdout.write('Could not import \'{}={}\', already exists.'.format(a.source, a.destination))
counts["existing"] += 1 # Exists
self.stdout.write(f"Could not import '{a.source}={a.destination}', already exists.")
# Print num imported, existing, total
self.stdout.write('Imported {}, skipped {} existing, total {}'.format(
counts['imported'],
counts['existing'],
len(new_aliases)))
self.stdout.write(
"Imported {}, skipped {} existing, total {}".format(
counts["imported"], counts["existing"], len(new_aliases)
)
)
+27 -31
View File
@@ -1,62 +1,58 @@
# -*- 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 = [
]
dependencies = []
operations = [
migrations.CreateModel(
name='Alias',
name="Alias",
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
('source', models.EmailField(max_length=254)),
('destination', models.EmailField(max_length=254)),
('active', models.BooleanField(default=True)),
("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, primary_key=True)),
("created", models.DateTimeField(auto_now_add=True)),
("updated", models.DateTimeField(auto_now=True)),
("source", models.EmailField(max_length=254)),
("destination", models.EmailField(max_length=254)),
("active", models.BooleanField(default=True)),
],
options={
'verbose_name_plural': 'aliases',
"verbose_name_plural": "aliases",
},
bases=(models.Model,),
),
migrations.CreateModel(
name='Domain',
name="Domain",
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
('name', models.CharField(unique=True, max_length=254)),
("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, primary_key=True)),
("created", models.DateTimeField(auto_now_add=True)),
("updated", models.DateTimeField(auto_now=True)),
("name", models.CharField(unique=True, max_length=254)),
],
options={
'abstract': False,
"abstract": False,
},
bases=(models.Model,),
),
migrations.CreateModel(
name='User',
name="User",
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
('email', models.EmailField(unique=True, max_length=254)),
('password', models.CharField(max_length=109)),
('domain', models.ForeignKey(to='dpdapi.Domain', on_delete=models.CASCADE)),
("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, primary_key=True)),
("created", models.DateTimeField(auto_now_add=True)),
("updated", models.DateTimeField(auto_now=True)),
("email", models.EmailField(unique=True, max_length=254)),
("password", models.CharField(max_length=109)),
("domain", models.ForeignKey(to="dpdapi.Domain", on_delete=models.CASCADE)),
],
options={
'abstract': False,
"abstract": False,
},
bases=(models.Model,),
),
migrations.AddField(
model_name='alias',
name='domain',
field=models.ForeignKey(to='dpdapi.Domain', on_delete=models.CASCADE),
model_name="alias",
name="domain",
field=models.ForeignKey(to="dpdapi.Domain", on_delete=models.CASCADE),
preserve_default=True,
),
]
+4 -7
View File
@@ -1,18 +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 = [
('dpdapi', '0001_initial'),
("dpdapi", "0001_initial"),
]
operations = [
migrations.AlterUniqueTogether(
name='alias',
unique_together=set([('source', 'destination', 'domain')]),
name="alias",
unique_together={("source", "destination", "domain")},
),
]
+3 -3
View File
@@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('dpdapi', '0002_auto_20150118_1638'),
("dpdapi", "0002_auto_20150118_1638"),
]
operations = [
migrations.AlterField(
model_name='user',
name='password',
model_name="user",
name="password",
field=models.CharField(max_length=512),
),
]
+8 -9
View File
@@ -12,21 +12,21 @@ class BaseModel(models.Model):
class Alias(BaseModel):
source = models.EmailField(max_length=254)
destination = models.EmailField(max_length=254)
domain = models.ForeignKey('dpdapi.Domain', models.CASCADE)
domain = models.ForeignKey("dpdapi.Domain", models.CASCADE)
active = models.BooleanField(default=True)
def __str__(self):
return "{}={}".format(self.source, self.destination)
return f"{self.source}={self.destination}"
def save(self, *args, **kwargs):
self.source = self.source.lower()
self.destination = self.destination.lower()
super(Alias, self).save(*args, **kwargs)
super().save(*args, **kwargs)
class Meta:
verbose_name_plural = 'aliases'
unique_together = ('source', 'destination', 'domain')
verbose_name_plural = "aliases"
unique_together = ("source", "destination", "domain")
class Domain(BaseModel):
@@ -38,13 +38,13 @@ class Domain(BaseModel):
def save(self, *args, **kwargs):
self.name = self.name.lower()
super(Domain, self).save(*args, **kwargs)
super().save(*args, **kwargs)
class User(BaseModel):
email = models.EmailField(max_length=254, unique=True)
password = models.CharField(max_length=512)
domain = models.ForeignKey('dpdapi.Domain', models.CASCADE)
domain = models.ForeignKey("dpdapi.Domain", models.CASCADE)
def __str__(self):
return self.email
@@ -52,5 +52,4 @@ class User(BaseModel):
def save(self, *args, **kwargs):
self.email = self.email.lower()
super(User, self).save(*args, **kwargs)
super().save(*args, **kwargs)
+10 -10
View File
@@ -1,10 +1,10 @@
from rest_framework import serializers
from passlib.hash import sha512_crypt
from rest_framework import serializers
from .models import Alias, Domain, User
class AliasSerializer(serializers.ModelSerializer):
def create(self, validated_data):
# Workaround to accept duplicates
# FIXME, will fail with existing alias with validated_data['active']=False
@@ -14,36 +14,36 @@ class AliasSerializer(serializers.ModelSerializer):
class Meta:
model = Alias
validators = [] # Disable unique togheter check
fields = '__all__'
fields = "__all__"
class AliasDeleteSerializer(serializers.ModelSerializer):
class Meta:
model = Alias
validators = [] # No Unique togheter check on deletion
fields = '__all__'
fields = "__all__"
class DomainSerializer(serializers.ModelSerializer):
class Meta:
model = Domain
fields = '__all__'
fields = "__all__"
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
extra_kwargs = {'password': {'write_only': True}}
fields = "__all__"
extra_kwargs = {"password": {"write_only": True}}
def _hash_password(self, raw_password):
return sha512_crypt.encrypt(raw_password)
def create(self, validated_data):
user = User(
email=validated_data['email'],
domain=validated_data['domain'],
password=self._hash_password(validated_data['password'])
email=validated_data["email"],
domain=validated_data["domain"],
password=self._hash_password(validated_data["password"]),
)
user.save()
return user
+106
View File
@@ -0,0 +1,106 @@
""" Ref: https://docs.djangoproject.com/en/3.2/topics/settings/ """
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
SECRET_KEY = os.getenv("SECRET_KEY", "hlaf#yma(8s!cy-bf5e6kc&_5n_l1h!34c(j32-b3v8o7pg$5n")
DEBUG = bool(os.getenv("DEBUG", True))
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.messages",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.staticfiles",
"django_extensions",
"rest_framework",
"django_filters",
]
INSTALLED_APPS += [
"dpdapi",
]
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",
]
ROOT_URLCONF = "dpdapi.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [
# insert your TEMPLATE_DIRS here
],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
WSGI_APPLICATION = "dpdapi.wsgi.application"
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": os.path.join(BASE_DIR, "db.sqlite3"),
}
}
LANGUAGE_CODE = "en-us"
TIME_ZONE = "Europe/Oslo"
USE_I18N = True
USE_L10N = True
USE_TZ = True
STATIC_URL = "/static/"
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework.authentication.BasicAuthentication",
"rest_framework.authentication.SessionAuthentication",
)
}
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"root": {
"level": "INFO",
"handlers": ["console"],
},
"formatters": {
"verbose": {"format": "%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s"},
},
"handlers": {"console": {"level": "DEBUG", "class": "logging.StreamHandler", "formatter": "verbose"}},
"loggers": {
"django.db.backends": {
"level": "ERROR",
"handlers": ["console"],
"propagate": False,
},
},
}
SENTRY_DSN = os.getenv("SENTRY_DSN", "https://c7e6a47844484c0a81a98e10e48c1b71@sentry.neuf.no/8")
if SENTRY_DSN:
SENTRY_ENVIRONMENT = os.getenv("SENTRY_ENVIRONMENT", "production")
sentry_sdk.init(dsn=SENTRY_DSN, environment=SENTRY_ENVIRONMENT, integrations=[DjangoIntegration()])
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
try:
from .local import * # noqa
except ImportError:
pass
+15 -17
View File
@@ -1,43 +1,41 @@
import json
from django.contrib.auth.models import User
from django.urls import reverse
from rest_framework.test import APITestCase
from dpdapi.models import Domain, Alias
from dpdapi.models import Alias, Domain
class RegexTestCase(APITestCase):
aliases = ['ops-alert@example.com', 'yolo-announce@example.com']
aliases = ["ops-alert@example.com", "yolo-announce@example.com"]
def setUp(self):
user = User.objects.create(username='yolo')
self.domain = Domain.objects.create(name='example.com')
user = User.objects.create(username="yolo")
self.domain = Domain.objects.create(name="example.com")
for a in self.aliases:
Alias.objects.create(source=a, destination='ninja@example.com', domain=self.domain)
Alias.objects.create(source=a, destination="ninja@example.com", domain=self.domain)
self.client.force_login(user)
def test_get_all(self):
res = self.client.get(reverse('alias-list'))
res = self.client.get(reverse("alias-list"))
self.assertEqual(len(res.data), len(self.aliases))
def test_domain_filter(self):
params = {'domain__name': 'example.com'}
res = self.client.get(reverse('alias-list'), data=params)
params = {"domain__name": "example.com"}
res = self.client.get(reverse("alias-list"), data=params)
self.assertEqual(len(res.data), len(self.aliases))
def test_domain_filter_no_hits(self):
params = {'domain__name': 'zexample.com'}
res = self.client.get(reverse('alias-list'), data=params)
params = {"domain__name": "zexample.com"}
res = self.client.get(reverse("alias-list"), data=params)
self.assertEqual(len(res.data), 0)
def test_regex_filter(self):
params = {'source__regex': '^ops-|^ops@'}
res = self.client.get(reverse('alias-list'), data=params)
params = {"source__regex": "^ops-|^ops@"}
res = self.client.get(reverse("alias-list"), data=params)
self.assertEqual(len(res.data), 1)
def test_delete_bulk_single(self):
data = {'destination': 'ninja@example.com', 'source': 'ding@example.com', 'domain': self.domain.pk}
Alias.objects.create(source=data['source'], destination=data['destination'], domain=self.domain)
res = self.client.delete(reverse('alias-delete-bulk'), data=[data], format='json')
data = {"destination": "ninja@example.com", "source": "ding@example.com", "domain": self.domain.pk}
Alias.objects.create(source=data["source"], destination=data["destination"], domain=self.domain)
res = self.client.delete(reverse("alias-delete-bulk"), data=[data], format="json")
self.assertEqual(res.status_code, 204, res.data)
+14 -5
View File
@@ -1,10 +1,19 @@
from django.contrib import admin
from django.urls import include, path, re_path
from django.views.generic import RedirectView
from rest_framework import urls as rest_framework_urls
from rest_framework.routers import DefaultRouter
from .views import AliasViewSet, DomainViewSet, UserViewSet
api_router = DefaultRouter()
api_router.register(r"aliases", AliasViewSet)
api_router.register(r"domains", DomainViewSet)
api_router.register(r"users", UserViewSet)
router = DefaultRouter()
router.register(r'aliases', AliasViewSet)
router.register(r'domains', DomainViewSet)
router.register(r'users', UserViewSet)
urlpatterns = router.urls
urlpatterns = [
path("", RedirectView.as_view(pattern_name="api-root")),
re_path(r"^api/", include(api_router.urls)),
re_path(r"^admin/", admin.site.urls),
re_path(r"^api-auth/", include(rest_framework_urls, namespace="rest_framework")),
]
+20 -14
View File
@@ -1,32 +1,36 @@
import logging
import operator
from functools import reduce
from django.db.models import Q
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.mixins import ListModelMixin
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework import status, viewsets
from rest_framework.viewsets import GenericViewSet
from dpdapi.filters import AliasRegexFilterBackend
from dpdapi.models import Alias, Domain, User
from dpdapi.serializers import AliasSerializer, AliasDeleteSerializer, DomainSerializer, UserSerializer
from dpdapi.serializers import AliasDeleteSerializer, AliasSerializer, DomainSerializer, UserSerializer
logger = logging.getLogger(__name__)
class AliasViewSet(ListModelMixin, GenericViewSet):
""" Aliases """
"""Aliases"""
queryset = Alias.objects.all()
serializer_class = AliasSerializer
permission_classes = [IsAuthenticated]
filter_backends = (DjangoFilterBackend, AliasRegexFilterBackend)
filterset_fields = ('domain', 'domain__name',)
filterset_fields = (
"domain",
"domain__name",
)
@action(detail=False, methods=['post'])
@action(detail=False, methods=["post"])
def create_bulk(self, request):
serializer = AliasSerializer(data=request.data, many=True)
@@ -35,7 +39,7 @@ class AliasViewSet(ListModelMixin, GenericViewSet):
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
@action(detail=False, methods=['delete'])
@action(detail=False, methods=["delete"])
def delete_bulk(self, request):
serializer = AliasDeleteSerializer(data=request.data, many=True)
@@ -45,14 +49,14 @@ class AliasViewSet(ListModelMixin, GenericViewSet):
q_list = map(lambda x: Q(**x), serializer.initial_data)
aliases = Alias.objects.filter(reduce(operator.or_, q_list))
initial_set = set(map(lambda x: (x['source'], x['destination'], x['domain']), serializer.initial_data))
deleted_set = set(aliases.values_list('source', 'destination', 'domain'))
initial_set = set(map(lambda x: (x["source"], x["destination"], x["domain"]), serializer.initial_data))
deleted_set = set(aliases.values_list("source", "destination", "domain"))
diff = initial_set.difference(deleted_set)
logger.debug('Deleted: {}'.format(','.join(map(lambda x: '{0[0]}={0[1]}'.format(x), deleted_set))))
logger.debug("Deleted: {}".format(",".join(map(lambda x: "{0[0]}={0[1]}".format(x), deleted_set))))
aliases.delete()
if diff:
diff_formatted = map(lambda x: '{0[0]}={0[1]}'.format(x), diff)
diff_formatted = map(lambda x: "{0[0]}={0[1]}".format(x), diff)
res = "Aliases '{}' could not be found and were not deleted.".format(", ".join(diff_formatted))
logger.info(res)
return Response({"result": res}, status=status.HTTP_200_OK)
@@ -61,16 +65,18 @@ class AliasViewSet(ListModelMixin, GenericViewSet):
class DomainViewSet(viewsets.ModelViewSet):
""" Domains """
"""Domains"""
queryset = Domain.objects.all()
serializer_class = DomainSerializer
permission_classes = [IsAuthenticated]
filter_backends = (DjangoFilterBackend,)
filterset_fields = ('name',)
filterset_fields = ("name",)
class UserViewSet(viewsets.ModelViewSet):
""" Users """
"""Users"""
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [IsAuthenticated]
+7
View File
@@ -0,0 +1,7 @@
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dpdapi.settings")
application = get_wsgi_application()
View File
Whitespace-only changes.
-150
View File
@@ -1,150 +0,0 @@
"""
For more information on this file, see
https://docs.djangoproject.com/en/1.7/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.7/ref/settings/
"""
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'hlaf#yma(8s!cy-bf5e6kc&_5n_l1h!34c(j32-b3v8o7pg$5n'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.messages',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.staticfiles',
'django_extensions',
'rest_framework',
'django_filters',
]
INSTALLED_APPS += [
'dpdapi',
]
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',
]
ROOT_URLCONF = 'dpdapisite.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
# insert your TEMPLATE_DIRS here
],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'dpdapisite.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.7/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Internationalization
# https://docs.djangoproject.com/en/1.7/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'Europe/Oslo'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.7/howto/static-files/
STATIC_URL = '/static/'
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
)
}
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'root': {
'level': 'INFO',
'handlers': ['console'],
},
'formatters': {
'verbose': {
'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
},
},
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'verbose'
}
},
'loggers': {
'django.db.backends': {
'level': 'ERROR',
'handlers': ['console'],
'propagate': False,
},
},
}
SENTRY_DSN = os.getenv('SENTRY_DSN', 'https://c7e6a47844484c0a81a98e10e48c1b71@sentry.neuf.no/8')
if SENTRY_DSN:
SENTRY_ENVIRONMENT = os.getenv('SENTRY_ENVIRONMENT', 'production')
sentry_sdk.init(
dsn=SENTRY_DSN,
environment=SENTRY_ENVIRONMENT,
integrations=[DjangoIntegration()]
)
try:
from .local_settings import *
except ImportError:
pass
-14
View File
@@ -1,14 +0,0 @@
from django.conf.urls import include, url
from django.contrib import admin
from django.views.generic import RedirectView
from rest_framework import urls as rest_framework_urls
from dpdapi import urls as dpdapi_urls
urlpatterns = [
url(r'^$', RedirectView.as_view(pattern_name='api-root')),
url(r'^api/', include(dpdapi_urls)),
url(r'^admin/', admin.site.urls),
url(r'^api-auth/', include(rest_framework_urls, namespace='rest_framework'))
]
-15
View File
@@ -1,15 +0,0 @@
"""
WSGI config for pfrsite project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dpdapisite.settings")
application = get_wsgi_application()
+1 -1
View File
@@ -3,7 +3,7 @@ import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dpdapisite.settings")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dpdapi.settings")
from django.core.management import execute_from_command_line
Generated
+991
View File
@@ -0,0 +1,991 @@
[[package]]
name = "asgiref"
version = "3.5.2"
description = "ASGI specs, helper code, and adapters"
category = "main"
optional = false
python-versions = ">=3.7"
[package.extras]
tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"]
[[package]]
name = "atomicwrites"
version = "1.4.1"
description = "Atomic file writes."
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "attrs"
version = "22.1.0"
description = "Classes Without Boilerplate"
category = "dev"
optional = false
python-versions = ">=3.5"
[package.extras]
dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"]
docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"]
tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "zope.interface", "cloudpickle"]
tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "mypy (>=0.900,!=0.940)", "pytest-mypy-plugins", "cloudpickle"]
[[package]]
name = "bcrypt"
version = "3.2.2"
description = "Modern password hashing for your software and your servers"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
cffi = ">=1.1"
[package.extras]
tests = ["pytest (>=3.2.1,!=3.3.0)"]
typecheck = ["mypy"]
[[package]]
name = "black"
version = "22.6.0"
description = "The uncompromising code formatter."
category = "dev"
optional = false
python-versions = ">=3.6.2"
[package.dependencies]
click = ">=8.0.0"
mypy-extensions = ">=0.4.3"
pathspec = ">=0.9.0"
platformdirs = ">=2"
tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""}
typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
[package.extras]
colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.7.4)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"]
[[package]]
name = "certifi"
version = "2022.6.15"
description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = false
python-versions = ">=3.6"
[[package]]
name = "cffi"
version = "1.15.1"
description = "Foreign Function Interface for Python calling C code."
category = "dev"
optional = false
python-versions = "*"
[package.dependencies]
pycparser = "*"
[[package]]
name = "cfgv"
version = "3.3.1"
description = "Validate configuration and produce human readable error messages."
category = "dev"
optional = false
python-versions = ">=3.6.1"
[[package]]
name = "charset-normalizer"
version = "2.1.0"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "dev"
optional = false
python-versions = ">=3.6.0"
[package.extras]
unicode_backport = ["unicodedata2"]
[[package]]
name = "click"
version = "8.1.3"
description = "Composable command line interface toolkit"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
[[package]]
name = "codecov"
version = "2.1.12"
description = "Hosted coverage reports for GitHub, Bitbucket and Gitlab"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.dependencies]
coverage = "*"
requests = ">=2.7.9"
[[package]]
name = "colorama"
version = "0.4.5"
description = "Cross-platform colored terminal text."
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "coverage"
version = "6.4.3"
description = "Code coverage measurement for Python"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""}
[package.extras]
toml = ["tomli"]
[[package]]
name = "cryptography"
version = "37.0.4"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
cffi = ">=1.12"
[package.extras]
docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"]
docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"]
pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
sdist = ["setuptools_rust (>=0.11.4)"]
ssh = ["bcrypt (>=3.1.5)"]
test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"]
[[package]]
name = "distlib"
version = "0.3.5"
description = "Distribution utilities"
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "django"
version = "3.2.15"
description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design."
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
asgiref = ">=3.3.2,<4"
pytz = "*"
sqlparse = ">=0.2.2"
[package.extras]
argon2 = ["argon2-cffi (>=19.1.0)"]
bcrypt = ["bcrypt"]
[[package]]
name = "django-extensions"
version = "3.2.0"
description = "Extensions for Django"
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
Django = ">=3.2"
[[package]]
name = "django-filter"
version = "22.1"
description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically."
category = "main"
optional = false
python-versions = ">=3.7"
[package.dependencies]
Django = ">=3.2"
[[package]]
name = "django-upgrade"
version = "1.7.0"
description = "Automatically upgrade your Django projects."
category = "dev"
optional = false
python-versions = ">=3.8"
[package.dependencies]
tokenize-rt = ">=4.1.0"
[[package]]
name = "djangorestframework"
version = "3.13.1"
description = "Web APIs for Django, made easy."
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
django = ">=2.2"
pytz = "*"
[[package]]
name = "fabric"
version = "2.7.1"
description = "High level SSH command execution"
category = "dev"
optional = false
python-versions = "*"
[package.dependencies]
invoke = ">=1.3,<2.0"
paramiko = ">=2.4"
pathlib2 = "*"
[package.extras]
pytest = ["mock (>=2.0.0,<3.0)", "pytest (>=3.2.5,<4.0)"]
testing = ["mock (>=2.0.0,<3.0)"]
[[package]]
name = "filelock"
version = "3.7.1"
description = "A platform independent file lock."
category = "dev"
optional = false
python-versions = ">=3.7"
[package.extras]
docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"]
testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"]
[[package]]
name = "gunicorn"
version = "20.1.0"
description = "WSGI HTTP Server for UNIX"
category = "main"
optional = false
python-versions = ">=3.5"
[package.extras]
eventlet = ["eventlet (>=0.24.1)"]
gevent = ["gevent (>=1.4.0)"]
setproctitle = ["setproctitle"]
tornado = ["tornado (>=0.2)"]
[[package]]
name = "identify"
version = "2.5.3"
description = "File identification library for Python"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.extras]
license = ["ukkonen"]
[[package]]
name = "idna"
version = "3.3"
description = "Internationalized Domain Names in Applications (IDNA)"
category = "dev"
optional = false
python-versions = ">=3.5"
[[package]]
name = "iniconfig"
version = "1.1.1"
description = "iniconfig: brain-dead simple config-ini parsing"
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "invoke"
version = "1.7.1"
description = "Pythonic task execution"
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "isort"
version = "5.10.1"
description = "A Python utility / library to sort Python imports."
category = "dev"
optional = false
python-versions = ">=3.6.1,<4.0"
[package.extras]
pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
requirements_deprecated_finder = ["pipreqs", "pip-api"]
colors = ["colorama (>=0.4.3,<0.5.0)"]
plugins = ["setuptools"]
[[package]]
name = "libcst"
version = "0.4.7"
description = "A concrete syntax tree with AST-like properties for Python 3.5, 3.6, 3.7, 3.8, 3.9, and 3.10 programs."
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
pyyaml = ">=5.2"
typing-extensions = ">=3.7.4.2"
typing-inspect = ">=0.4.0"
[package.extras]
dev = ["black (==22.3.0)", "coverage (>=4.5.4)", "fixit (==0.1.1)", "flake8 (>=3.7.8)", "hypothesis (>=4.36.0)", "hypothesmith (>=0.0.4)", "jupyter (>=1.0.0)", "maturin (>=0.8.3,<0.9)", "nbsphinx (>=0.4.2)", "prompt-toolkit (>=2.0.9)", "setuptools-scm (>=6.0.1)", "sphinx-rtd-theme (>=0.4.3)", "ufmt (==1.3)", "usort (==1.0.0rc1)", "setuptools-rust (>=0.12.1)", "slotscheck (>=0.7.1)", "jinja2 (==3.0.3)", "pyre-check (==0.9.9)"]
[[package]]
name = "mypy-extensions"
version = "0.4.3"
description = "Experimental type system extensions for programs checked with the mypy typechecker."
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "mysqlclient"
version = "2.1.1"
description = "Python interface to MySQL"
category = "main"
optional = false
python-versions = ">=3.5"
[[package]]
name = "nodeenv"
version = "1.7.0"
description = "Node.js virtual environment builder"
category = "dev"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*"
[[package]]
name = "packaging"
version = "21.3"
description = "Core utilities for Python packages"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
[[package]]
name = "paramiko"
version = "2.11.0"
description = "SSH2 protocol library"
category = "dev"
optional = false
python-versions = "*"
[package.dependencies]
bcrypt = ">=3.1.3"
cryptography = ">=2.5"
pynacl = ">=1.0.1"
six = "*"
[package.extras]
all = ["pyasn1 (>=0.1.7)", "pynacl (>=1.0.1)", "bcrypt (>=3.1.3)", "invoke (>=1.3)", "gssapi (>=1.4.1)", "pywin32 (>=2.1.8)"]
ed25519 = ["pynacl (>=1.0.1)", "bcrypt (>=3.1.3)"]
gssapi = ["pyasn1 (>=0.1.7)", "gssapi (>=1.4.1)", "pywin32 (>=2.1.8)"]
invoke = ["invoke (>=1.3)"]
[[package]]
name = "passlib"
version = "1.7.4"
description = "comprehensive password hashing framework supporting over 30 schemes"
category = "main"
optional = false
python-versions = "*"
[package.extras]
argon2 = ["argon2-cffi (>=18.2.0)"]
bcrypt = ["bcrypt (>=3.1.0)"]
build_docs = ["sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)", "cloud-sptheme (>=1.10.1)"]
totp = ["cryptography"]
[[package]]
name = "pathlib2"
version = "2.3.7.post1"
description = "Object-oriented filesystem paths"
category = "dev"
optional = false
python-versions = "*"
[package.dependencies]
six = "*"
[[package]]
name = "pathspec"
version = "0.9.0"
description = "Utility library for gitignore style pattern matching of file paths."
category = "dev"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
[[package]]
name = "platformdirs"
version = "2.5.2"
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev"
optional = false
python-versions = ">=3.7"
[package.extras]
docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"]
test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"]
[[package]]
name = "pluggy"
version = "1.0.0"
description = "plugin and hook calling mechanisms for python"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.extras]
dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "pre-commit"
version = "2.20.0"
description = "A framework for managing and maintaining multi-language pre-commit hooks."
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
cfgv = ">=2.0.0"
identify = ">=1.0.0"
nodeenv = ">=0.11.1"
pyyaml = ">=5.1"
toml = "*"
virtualenv = ">=20.0.8"
[[package]]
name = "py"
version = "1.11.0"
description = "library with cross-python path, ini-parsing, io, code, log facilities"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "pycln"
version = "2.1.1"
description = "A formatter for finding and removing unused import statements."
category = "dev"
optional = false
python-versions = ">=3.6.2,<4"
[package.dependencies]
libcst = ">=0.3.10,<0.5.0"
pathspec = ">=0.9.0,<0.10.0"
pyyaml = ">=5.3.1,<7.0.0"
tomlkit = ">=0.11.1,<0.12.0"
typer = ">=0.4.1,<0.7.0"
[[package]]
name = "pycparser"
version = "2.21"
description = "C parser in Python"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "pynacl"
version = "1.5.0"
description = "Python binding to the Networking and Cryptography (NaCl) library"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
cffi = ">=1.4.1"
[package.extras]
docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"]
tests = ["pytest (>=3.2.1,!=3.3.0)", "hypothesis (>=3.27.0)"]
[[package]]
name = "pyparsing"
version = "3.0.9"
description = "pyparsing module - Classes and methods to define and execute parsing grammars"
category = "dev"
optional = false
python-versions = ">=3.6.8"
[package.extras]
diagrams = ["railroad-diagrams", "jinja2"]
[[package]]
name = "pytest"
version = "7.1.2"
description = "pytest: simple powerful testing with Python"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
attrs = ">=19.2.0"
colorama = {version = "*", markers = "sys_platform == \"win32\""}
iniconfig = "*"
packaging = "*"
pluggy = ">=0.12,<2.0"
py = ">=1.8.2"
tomli = ">=1.0.0"
[package.extras]
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"]
[[package]]
name = "pytest-cov"
version = "3.0.0"
description = "Pytest plugin for measuring coverage."
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
coverage = {version = ">=5.2.1", extras = ["toml"]}
pytest = ">=4.6"
[package.extras]
testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"]
[[package]]
name = "pytest-django"
version = "4.5.2"
description = "A Django plugin for pytest."
category = "dev"
optional = false
python-versions = ">=3.5"
[package.dependencies]
pytest = ">=5.4.0"
[package.extras]
docs = ["sphinx", "sphinx-rtd-theme"]
testing = ["django", "django-configurations (>=2.0)"]
[[package]]
name = "pytz"
version = "2022.1"
description = "World timezone definitions, modern and historical"
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "pyupgrade"
version = "2.37.3"
description = "A tool to automatically upgrade syntax for newer versions."
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
tokenize-rt = ">=3.2.0"
[[package]]
name = "pyyaml"
version = "6.0"
description = "YAML parser and emitter for Python"
category = "dev"
optional = false
python-versions = ">=3.6"
[[package]]
name = "requests"
version = "2.28.1"
description = "Python HTTP for Humans."
category = "dev"
optional = false
python-versions = ">=3.7, <4"
[package.dependencies]
certifi = ">=2017.4.17"
charset-normalizer = ">=2,<3"
idna = ">=2.5,<4"
urllib3 = ">=1.21.1,<1.27"
[package.extras]
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "sentry-sdk"
version = "1.9.2"
description = "Python client for Sentry (https://sentry.io)"
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
certifi = "*"
urllib3 = [
{version = ">=1.26.9", markers = "python_version >= \"3.5\""},
{version = ">=1.26.11", markers = "python_version >= \"3.6\""},
]
[package.extras]
aiohttp = ["aiohttp (>=3.5)"]
beam = ["apache-beam (>=2.12)"]
bottle = ["bottle (>=0.12.13)"]
celery = ["celery (>=3)"]
chalice = ["chalice (>=1.16.0)"]
django = ["django (>=1.8)"]
falcon = ["falcon (>=1.4)"]
fastapi = ["fastapi (>=0.79.0)"]
flask = ["flask (>=0.11)", "blinker (>=1.1)"]
httpx = ["httpx (>=0.16.0)"]
pure_eval = ["pure-eval", "executing", "asttokens"]
pyspark = ["pyspark (>=2.4.4)"]
quart = ["quart (>=0.16.1)", "blinker (>=1.1)"]
rq = ["rq (>=0.6)"]
sanic = ["sanic (>=0.8)"]
sqlalchemy = ["sqlalchemy (>=1.2)"]
starlette = ["starlette (>=0.19.1)"]
tornado = ["tornado (>=5)"]
[[package]]
name = "six"
version = "1.16.0"
description = "Python 2 and 3 compatibility utilities"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "sqlparse"
version = "0.4.2"
description = "A non-validating SQL parser."
category = "main"
optional = false
python-versions = ">=3.5"
[[package]]
name = "tokenize-rt"
version = "4.2.1"
description = "A wrapper around the stdlib `tokenize` which roundtrips."
category = "dev"
optional = false
python-versions = ">=3.6.1"
[[package]]
name = "toml"
version = "0.10.2"
description = "Python Library for Tom's Obvious, Minimal Language"
category = "dev"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
category = "dev"
optional = false
python-versions = ">=3.7"
[[package]]
name = "tomlkit"
version = "0.11.1"
description = "Style preserving TOML library"
category = "dev"
optional = false
python-versions = ">=3.6,<4.0"
[[package]]
name = "typer"
version = "0.6.1"
description = "Typer, build great CLIs. Easy to code. Based on Python type hints."
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
click = ">=7.1.1,<9.0.0"
[package.extras]
all = ["colorama (>=0.4.3,<0.5.0)", "shellingham (>=1.3.0,<2.0.0)", "rich (>=10.11.0,<13.0.0)"]
dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "pre-commit (>=2.17.0,<3.0.0)"]
doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "mdx-include (>=1.4.1,<2.0.0)"]
test = ["shellingham (>=1.3.0,<2.0.0)", "pytest (>=4.4.0,<5.4.0)", "pytest-cov (>=2.10.0,<3.0.0)", "coverage (>=5.2,<6.0)", "pytest-xdist (>=1.32.0,<2.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "mypy (==0.910)", "black (>=22.3.0,<23.0.0)", "isort (>=5.0.6,<6.0.0)", "rich (>=10.11.0,<13.0.0)"]
[[package]]
name = "typing-extensions"
version = "4.3.0"
description = "Backported and Experimental Type Hints for Python 3.7+"
category = "dev"
optional = false
python-versions = ">=3.7"
[[package]]
name = "typing-inspect"
version = "0.7.1"
description = "Runtime inspection utilities for typing module."
category = "dev"
optional = false
python-versions = "*"
[package.dependencies]
mypy-extensions = ">=0.3.0"
typing-extensions = ">=3.7.4"
[[package]]
name = "urllib3"
version = "1.26.11"
description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4"
[package.extras]
brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"]
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]]
name = "virtualenv"
version = "20.16.3"
description = "Virtual Python Environment builder"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
distlib = ">=0.3.5,<1"
filelock = ">=3.4.1,<4"
platformdirs = ">=2.4,<3"
[package.extras]
docs = ["proselint (>=0.13)", "sphinx (>=5.1.1)", "sphinx-argparse (>=0.3.1)", "sphinx-rtd-theme (>=1)", "towncrier (>=21.9)"]
testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"]
[metadata]
lock-version = "1.1"
python-versions = "^3.8"
content-hash = "c0829f4c7197c9f5658a2b3ea22927240b0e53c0948a27d109314d338b808c1d"
[metadata.files]
asgiref = [
{file = "asgiref-3.5.2-py3-none-any.whl", hash = "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4"},
{file = "asgiref-3.5.2.tar.gz", hash = "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"},
]
atomicwrites = []
attrs = []
bcrypt = []
black = []
certifi = [
{file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"},
{file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"},
]
cffi = []
cfgv = [
{file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"},
{file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"},
]
charset-normalizer = []
click = [
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
]
codecov = [
{file = "codecov-2.1.12-py2.py3-none-any.whl", hash = "sha256:585dc217dc3d8185198ceb402f85d5cb5dbfa0c5f350a5abcdf9e347776a5b47"},
{file = "codecov-2.1.12-py3.8.egg", hash = "sha256:782a8e5352f22593cbc5427a35320b99490eb24d9dcfa2155fd99d2b75cfb635"},
{file = "codecov-2.1.12.tar.gz", hash = "sha256:a0da46bb5025426da895af90938def8ee12d37fcbcbbbc15b6dc64cf7ebc51c1"},
]
colorama = [
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
]
coverage = []
cryptography = []
distlib = []
django = []
django-extensions = []
django-filter = []
django-upgrade = []
djangorestframework = [
{file = "djangorestframework-3.13.1-py3-none-any.whl", hash = "sha256:24c4bf58ed7e85d1fe4ba250ab2da926d263cd57d64b03e8dcef0ac683f8b1aa"},
{file = "djangorestframework-3.13.1.tar.gz", hash = "sha256:0c33407ce23acc68eca2a6e46424b008c9c02eceb8cf18581921d0092bc1f2ee"},
]
fabric = []
filelock = [
{file = "filelock-3.7.1-py3-none-any.whl", hash = "sha256:37def7b658813cda163b56fc564cdc75e86d338246458c4c28ae84cabefa2404"},
{file = "filelock-3.7.1.tar.gz", hash = "sha256:3a0fd85166ad9dbab54c9aec96737b744106dc5f15c0b09a6744a445299fcf04"},
]
gunicorn = [
{file = "gunicorn-20.1.0-py3-none-any.whl", hash = "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e"},
{file = "gunicorn-20.1.0.tar.gz", hash = "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8"},
]
identify = []
idna = [
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
]
iniconfig = [
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
{file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"},
]
invoke = []
isort = [
{file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
{file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
]
libcst = []
mypy-extensions = [
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
]
mysqlclient = []
nodeenv = []
packaging = [
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
]
paramiko = []
passlib = [
{file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"},
{file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"},
]
pathlib2 = [
{file = "pathlib2-2.3.7.post1-py2.py3-none-any.whl", hash = "sha256:5266a0fd000452f1b3467d782f079a4343c63aaa119221fbdc4e39577489ca5b"},
{file = "pathlib2-2.3.7.post1.tar.gz", hash = "sha256:9fe0edad898b83c0c3e199c842b27ed216645d2e177757b2dd67384d4113c641"},
]
pathspec = [
{file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
]
platformdirs = [
{file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
{file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
]
pluggy = [
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
]
pre-commit = []
py = [
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
{file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"},
]
pycln = []
pycparser = [
{file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
{file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
]
pynacl = [
{file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"},
{file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"},
{file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"},
{file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"},
{file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"},
{file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"},
{file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"},
{file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"},
{file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"},
{file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"},
]
pyparsing = [
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
]
pytest = [
{file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"},
{file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"},
]
pytest-cov = []
pytest-django = [
{file = "pytest-django-4.5.2.tar.gz", hash = "sha256:d9076f759bb7c36939dbdd5ae6633c18edfc2902d1a69fdbefd2426b970ce6c2"},
{file = "pytest_django-4.5.2-py3-none-any.whl", hash = "sha256:c60834861933773109334fe5a53e83d1ef4828f2203a1d6a0fa9972f4f75ab3e"},
]
pytz = [
{file = "pytz-2022.1-py2.py3-none-any.whl", hash = "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"},
{file = "pytz-2022.1.tar.gz", hash = "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7"},
]
pyupgrade = []
pyyaml = [
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
{file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"},
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"},
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"},
{file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"},
{file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"},
{file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"},
{file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"},
{file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"},
{file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"},
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"},
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"},
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"},
{file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"},
{file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"},
{file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"},
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"},
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"},
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"},
{file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"},
{file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"},
{file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"},
{file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"},
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"},
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"},
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"},
{file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"},
{file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
]
requests = []
sentry-sdk = []
six = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
sqlparse = [
{file = "sqlparse-0.4.2-py3-none-any.whl", hash = "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d"},
{file = "sqlparse-0.4.2.tar.gz", hash = "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae"},
]
tokenize-rt = [
{file = "tokenize_rt-4.2.1-py2.py3-none-any.whl", hash = "sha256:08a27fa032a81cf45e8858d0ac706004fcd523e8463415ddf1442be38e204ea8"},
{file = "tokenize_rt-4.2.1.tar.gz", hash = "sha256:0d4f69026fed520f8a1e0103aa36c406ef4661417f20ca643f913e33531b3b94"},
]
toml = [
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
]
tomli = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
tomlkit = []
typer = []
typing-extensions = []
typing-inspect = [
{file = "typing_inspect-0.7.1-py2-none-any.whl", hash = "sha256:b1f56c0783ef0f25fb064a01be6e5407e54cf4a4bf4f3ba3fe51e0bd6dcea9e5"},
{file = "typing_inspect-0.7.1-py3-none-any.whl", hash = "sha256:3cd7d4563e997719a710a3bfe7ffb544c6b72069b6812a02e9b414a8fa3aaa6b"},
{file = "typing_inspect-0.7.1.tar.gz", hash = "sha256:047d4097d9b17f46531bf6f014356111a1b6fb821a24fe7ac909853ca2a782aa"},
]
urllib3 = []
virtualenv = []
+47
View File
@@ -0,0 +1,47 @@
[tool.pytest.ini_options]
DJANGO_SETTINGS_MODULE = "dpdapi.settings"
# FIXME: https://github.com/django-extensions/django-extensions/pull/1741
filterwarnings = "ignore::PendingDeprecationWarning:django.apps.registry.*"
[tool.black]
line-length = 120
target_version = ['py38']
[tool.isort]
profile = "black"
line_length = 120
[tool.poetry]
name = "django-postfix-dovecot-api"
version = "0.1.0"
description = ""
authors = ["EDB <edb@neuf.no>"]
[tool.poetry.dependencies]
python = "^3.8"
Django="3.2.*"
django-extensions="3.2.*"
django-filter="22.1"
djangorestframework="3.13.*"
gunicorn="20.1.*"
mysqlclient="2.1.*"
passlib="1.7.*"
sentry-sdk="1.9.*"
[tool.poetry.dev-dependencies]
black = "22.6.*"
codecov = "2.1.*"
django-upgrade = "^1.7.0"
fabric="2.7.*"
isort = "*"
pre-commit = "*"
pycln = "2.1.*"
pytest = "*"
pytest-cov = "^3.0.0"
pytest-django = "*"
pyupgrade = "2.37.*"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
-9
View File
@@ -1,9 +0,0 @@
Django==2.2.*
fabric==2.5.*
django-extensions==2.2.*
django-filter==2.2.*
djangorestframework==3.11.*
gunicorn==20.0.*
mysqlclient==1.4.6
passlib==1.7.*
sentry-sdk==0.13.*