Compare commits
2
Commits
main
...
chore/modernize
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
419857f195 | ||
|
|
b12413ed67 |
No files matched your search
@@ -2,5 +2,6 @@
|
|||||||
*.pyc
|
*.pyc
|
||||||
.idea
|
.idea
|
||||||
venv
|
venv
|
||||||
|
dpdapi/local.py
|
||||||
local_settings.py
|
local_settings.py
|
||||||
db.sqlite3
|
db.sqlite3
|
||||||
+38
-15
@@ -1,19 +1,42 @@
|
|||||||
image: python:latest
|
default:
|
||||||
|
image: python:3.8-slim
|
||||||
variables:
|
cache:
|
||||||
PIP_CACHE_DIR: "$CI_PROJECT_DIR/pip-cache"
|
|
||||||
|
|
||||||
cache:
|
|
||||||
paths:
|
paths:
|
||||||
- "$CI_PROJECT_DIR/pip-cache"
|
- .cache/pip
|
||||||
key: "$CI_PROJECT_ID"
|
- .venv
|
||||||
|
|
||||||
before_script:
|
|
||||||
- pip install -r requirements.txt
|
|
||||||
- pip install coverage
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
|
before_script:
|
||||||
|
- poetry install
|
||||||
script:
|
script:
|
||||||
- coverage run --source=dpdapi --omit=dpdapi/migrations/* manage.py test -v2
|
- bin/ensure-python-deps.sh
|
||||||
- coverage report
|
- poetry run python manage.py migrate
|
||||||
coverage: '/TOTAL.+ ([0-9]{1,3}%)/'
|
- 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
|
||||||
@@ -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"]
|
||||||
@@ -1,14 +1,20 @@
|
|||||||
# Install
|
# django-postfix-dovecot-api
|
||||||
apt install python-dev libmysqlclient-dev python-virtualenv
|
|
||||||
python -m venv venv
|
## Getting started
|
||||||
. venv/bin/activate
|
|
||||||
pip install -r requirements.txt
|
```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:
|
First you need to have your public key in `/home/gitdeploy/.ssh/authorized_keys` on the server and then you can do:
|
||||||
|
|
||||||
fab deploy
|
fab deploy
|
||||||
|
|
||||||
# Inspiration:
|
## Inspiration:
|
||||||
|
-
|
||||||
- http://modoboa.readthedocs.org/
|
- http://modoboa.readthedocs.org/
|
||||||
- https://github.com/lgunsch/django-vmail
|
- https://github.com/lgunsch/django-vmail
|
||||||
Executable
+18
@@ -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
|
||||||
Executable
+6
@@ -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
@@ -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')
|
||||||
+3
-3
@@ -5,9 +5,9 @@ from dpdapi.models import Alias, Domain, User
|
|||||||
|
|
||||||
@admin.register(Alias)
|
@admin.register(Alias)
|
||||||
class AliasAdmin(admin.ModelAdmin):
|
class AliasAdmin(admin.ModelAdmin):
|
||||||
list_display = ['source', 'destination', 'active', 'domain']
|
list_display = ["source", "destination", "active", "domain"]
|
||||||
list_filter = ['domain']
|
list_filter = ["domain"]
|
||||||
search_fields = ['source', 'destination']
|
search_fields = ["source", "destination"]
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Domain)
|
@admin.register(Domain)
|
||||||
|
|||||||
+1
-4
@@ -7,10 +7,7 @@ from dpdapi.models import Alias
|
|||||||
class AliasRegexFilter(django_filters.FilterSet):
|
class AliasRegexFilter(django_filters.FilterSet):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Alias
|
model = Alias
|
||||||
fields = {
|
fields = {"source": ["iregex", "regex"], "destination": ["iregex", "regex"]}
|
||||||
'source': ['iregex', 'regex'],
|
|
||||||
'destination': ['iregex', 'regex']
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class AliasRegexFilterBackend(BaseFilterBackend):
|
class AliasRegexFilterBackend(BaseFilterBackend):
|
||||||
|
|||||||
@@ -1,60 +1,62 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.db.utils import IntegrityError
|
from django.db.utils import IntegrityError
|
||||||
import os
|
|
||||||
|
|
||||||
from dpdapi.models import Alias, Domain
|
from dpdapi.models import Alias, Domain
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
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):
|
def handle(self, *args, **options):
|
||||||
if len(args) == 1:
|
if len(args) == 1:
|
||||||
if os.path.exists(args[0]):
|
if os.path.exists(args[0]):
|
||||||
self.import_file(args[0])
|
self.import_file(args[0])
|
||||||
else:
|
else:
|
||||||
self.stdout.write("{} does not exist".format(args[0]))
|
self.stdout.write(f"{args[0]} does not exist")
|
||||||
else:
|
else:
|
||||||
self.stdout.write("Missing argument")
|
self.stdout.write("Missing argument")
|
||||||
|
|
||||||
def import_file(self, filename):
|
def import_file(self, filename):
|
||||||
new_aliases = []
|
new_aliases = []
|
||||||
counts = {'imported': 0, 'existing': 0}
|
counts = {"imported": 0, "existing": 0}
|
||||||
# Open file
|
# Open file
|
||||||
with open(filename) as f:
|
with open(filename) as f:
|
||||||
# Parse
|
# Parse
|
||||||
for l in f:
|
for l in f:
|
||||||
if l.startswith('*: '):
|
if l.startswith("*: "):
|
||||||
continue # Skip catch all
|
continue # Skip catch all
|
||||||
|
|
||||||
source, destinations = l.strip().split(': ')
|
source, destinations = l.strip().split(": ")
|
||||||
for destination in destinations.split(','):
|
for destination in destinations.split(","):
|
||||||
if destination.startswith('"|') or '-admin@' in destination:
|
if destination.startswith('"|') or "-admin@" in destination:
|
||||||
continue # Skip mailman-aliases
|
continue # Skip mailman-aliases
|
||||||
new_aliases.append(Alias(source=source, destination=destination))
|
new_aliases.append(Alias(source=source, destination=destination))
|
||||||
|
|
||||||
if len(new_aliases) == 0:
|
if len(new_aliases) == 0:
|
||||||
self.stdout.write('No aliases found')
|
self.stdout.write("No aliases found")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Lookup domain
|
# 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)
|
d, created = Domain.objects.get_or_create(name=domain_name)
|
||||||
if created:
|
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
|
# Try to create, log if exists. Slow but works
|
||||||
for a in new_aliases:
|
for a in new_aliases:
|
||||||
a.domain = d
|
a.domain = d
|
||||||
try:
|
try:
|
||||||
a.save()
|
a.save()
|
||||||
counts['imported'] += 1 # Woop!
|
counts["imported"] += 1 # Woop!
|
||||||
except IntegrityError:
|
except IntegrityError:
|
||||||
counts['existing'] += 1 # Exists
|
counts["existing"] += 1 # Exists
|
||||||
self.stdout.write('Could not import \'{}={}\', already exists.'.format(a.source, a.destination))
|
self.stdout.write(f"Could not import '{a.source}={a.destination}', already exists.")
|
||||||
|
|
||||||
# Print num imported, existing, total
|
# Print num imported, existing, total
|
||||||
self.stdout.write('Imported {}, skipped {} existing, total {}'.format(
|
self.stdout.write(
|
||||||
counts['imported'],
|
"Imported {}, skipped {} existing, total {}".format(
|
||||||
counts['existing'],
|
counts["imported"], counts["existing"], len(new_aliases)
|
||||||
len(new_aliases)))
|
)
|
||||||
|
)
|
||||||
@@ -1,62 +1,58 @@
|
|||||||
# -*- coding: utf-8 -*-
|
from django.db import migrations, models
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = []
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Alias',
|
name="Alias",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, primary_key=True)),
|
||||||
('created', models.DateTimeField(auto_now_add=True)),
|
("created", models.DateTimeField(auto_now_add=True)),
|
||||||
('updated', models.DateTimeField(auto_now=True)),
|
("updated", models.DateTimeField(auto_now=True)),
|
||||||
('source', models.EmailField(max_length=254)),
|
("source", models.EmailField(max_length=254)),
|
||||||
('destination', models.EmailField(max_length=254)),
|
("destination", models.EmailField(max_length=254)),
|
||||||
('active', models.BooleanField(default=True)),
|
("active", models.BooleanField(default=True)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name_plural': 'aliases',
|
"verbose_name_plural": "aliases",
|
||||||
},
|
},
|
||||||
bases=(models.Model,),
|
bases=(models.Model,),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Domain',
|
name="Domain",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, primary_key=True)),
|
||||||
('created', models.DateTimeField(auto_now_add=True)),
|
("created", models.DateTimeField(auto_now_add=True)),
|
||||||
('updated', models.DateTimeField(auto_now=True)),
|
("updated", models.DateTimeField(auto_now=True)),
|
||||||
('name', models.CharField(unique=True, max_length=254)),
|
("name", models.CharField(unique=True, max_length=254)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'abstract': False,
|
"abstract": False,
|
||||||
},
|
},
|
||||||
bases=(models.Model,),
|
bases=(models.Model,),
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='User',
|
name="User",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
("id", models.AutoField(verbose_name="ID", serialize=False, auto_created=True, primary_key=True)),
|
||||||
('created', models.DateTimeField(auto_now_add=True)),
|
("created", models.DateTimeField(auto_now_add=True)),
|
||||||
('updated', models.DateTimeField(auto_now=True)),
|
("updated", models.DateTimeField(auto_now=True)),
|
||||||
('email', models.EmailField(unique=True, max_length=254)),
|
("email", models.EmailField(unique=True, max_length=254)),
|
||||||
('password', models.CharField(max_length=109)),
|
("password", models.CharField(max_length=109)),
|
||||||
('domain', models.ForeignKey(to='dpdapi.Domain', on_delete=models.CASCADE)),
|
("domain", models.ForeignKey(to="dpdapi.Domain", on_delete=models.CASCADE)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'abstract': False,
|
"abstract": False,
|
||||||
},
|
},
|
||||||
bases=(models.Model,),
|
bases=(models.Model,),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='alias',
|
model_name="alias",
|
||||||
name='domain',
|
name="domain",
|
||||||
field=models.ForeignKey(to='dpdapi.Domain', on_delete=models.CASCADE),
|
field=models.ForeignKey(to="dpdapi.Domain", on_delete=models.CASCADE),
|
||||||
preserve_default=True,
|
preserve_default=True,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@@ -1,18 +1,15 @@
|
|||||||
# -*- coding: utf-8 -*-
|
from django.db import migrations, models
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('dpdapi', '0001_initial'),
|
("dpdapi", "0001_initial"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterUniqueTogether(
|
migrations.AlterUniqueTogether(
|
||||||
name='alias',
|
name="alias",
|
||||||
unique_together=set([('source', 'destination', 'domain')]),
|
unique_together={("source", "destination", "domain")},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@@ -6,13 +6,13 @@ from django.db import migrations, models
|
|||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('dpdapi', '0002_auto_20150118_1638'),
|
("dpdapi", "0002_auto_20150118_1638"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='user',
|
model_name="user",
|
||||||
name='password',
|
name="password",
|
||||||
field=models.CharField(max_length=512),
|
field=models.CharField(max_length=512),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
+8
-9
@@ -12,21 +12,21 @@ class BaseModel(models.Model):
|
|||||||
class Alias(BaseModel):
|
class Alias(BaseModel):
|
||||||
source = models.EmailField(max_length=254)
|
source = models.EmailField(max_length=254)
|
||||||
destination = 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)
|
active = models.BooleanField(default=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "{}={}".format(self.source, self.destination)
|
return f"{self.source}={self.destination}"
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
self.source = self.source.lower()
|
self.source = self.source.lower()
|
||||||
self.destination = self.destination.lower()
|
self.destination = self.destination.lower()
|
||||||
|
|
||||||
super(Alias, self).save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name_plural = 'aliases'
|
verbose_name_plural = "aliases"
|
||||||
unique_together = ('source', 'destination', 'domain')
|
unique_together = ("source", "destination", "domain")
|
||||||
|
|
||||||
|
|
||||||
class Domain(BaseModel):
|
class Domain(BaseModel):
|
||||||
@@ -38,13 +38,13 @@ class Domain(BaseModel):
|
|||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
self.name = self.name.lower()
|
self.name = self.name.lower()
|
||||||
|
|
||||||
super(Domain, self).save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class User(BaseModel):
|
class User(BaseModel):
|
||||||
email = models.EmailField(max_length=254, unique=True)
|
email = models.EmailField(max_length=254, unique=True)
|
||||||
password = models.CharField(max_length=512)
|
password = models.CharField(max_length=512)
|
||||||
domain = models.ForeignKey('dpdapi.Domain', models.CASCADE)
|
domain = models.ForeignKey("dpdapi.Domain", models.CASCADE)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.email
|
return self.email
|
||||||
@@ -52,5 +52,4 @@ class User(BaseModel):
|
|||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
self.email = self.email.lower()
|
self.email = self.email.lower()
|
||||||
|
|
||||||
super(User, self).save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
+10
-10
@@ -1,10 +1,10 @@
|
|||||||
from rest_framework import serializers
|
|
||||||
from passlib.hash import sha512_crypt
|
from passlib.hash import sha512_crypt
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
from .models import Alias, Domain, User
|
from .models import Alias, Domain, User
|
||||||
|
|
||||||
|
|
||||||
class AliasSerializer(serializers.ModelSerializer):
|
class AliasSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
# Workaround to accept duplicates
|
# Workaround to accept duplicates
|
||||||
# FIXME, will fail with existing alias with validated_data['active']=False
|
# FIXME, will fail with existing alias with validated_data['active']=False
|
||||||
@@ -14,36 +14,36 @@ class AliasSerializer(serializers.ModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Alias
|
model = Alias
|
||||||
validators = [] # Disable unique togheter check
|
validators = [] # Disable unique togheter check
|
||||||
fields = '__all__'
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
class AliasDeleteSerializer(serializers.ModelSerializer):
|
class AliasDeleteSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Alias
|
model = Alias
|
||||||
validators = [] # No Unique togheter check on deletion
|
validators = [] # No Unique togheter check on deletion
|
||||||
fields = '__all__'
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
class DomainSerializer(serializers.ModelSerializer):
|
class DomainSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Domain
|
model = Domain
|
||||||
fields = '__all__'
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
class UserSerializer(serializers.ModelSerializer):
|
class UserSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = '__all__'
|
fields = "__all__"
|
||||||
extra_kwargs = {'password': {'write_only': True}}
|
extra_kwargs = {"password": {"write_only": True}}
|
||||||
|
|
||||||
def _hash_password(self, raw_password):
|
def _hash_password(self, raw_password):
|
||||||
return sha512_crypt.encrypt(raw_password)
|
return sha512_crypt.encrypt(raw_password)
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
user = User(
|
user = User(
|
||||||
email=validated_data['email'],
|
email=validated_data["email"],
|
||||||
domain=validated_data['domain'],
|
domain=validated_data["domain"],
|
||||||
password=self._hash_password(validated_data['password'])
|
password=self._hash_password(validated_data["password"]),
|
||||||
)
|
)
|
||||||
user.save()
|
user.save()
|
||||||
return user
|
return user
|
||||||
@@ -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
@@ -1,43 +1,41 @@
|
|||||||
import json
|
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from rest_framework.test import APITestCase
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
from dpdapi.models import Domain, Alias
|
from dpdapi.models import Alias, Domain
|
||||||
|
|
||||||
|
|
||||||
class RegexTestCase(APITestCase):
|
class RegexTestCase(APITestCase):
|
||||||
aliases = ['ops-alert@example.com', 'yolo-announce@example.com']
|
aliases = ["ops-alert@example.com", "yolo-announce@example.com"]
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
user = User.objects.create(username='yolo')
|
user = User.objects.create(username="yolo")
|
||||||
self.domain = Domain.objects.create(name='example.com')
|
self.domain = Domain.objects.create(name="example.com")
|
||||||
for a in self.aliases:
|
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)
|
self.client.force_login(user)
|
||||||
|
|
||||||
def test_get_all(self):
|
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))
|
self.assertEqual(len(res.data), len(self.aliases))
|
||||||
|
|
||||||
def test_domain_filter(self):
|
def test_domain_filter(self):
|
||||||
params = {'domain__name': 'example.com'}
|
params = {"domain__name": "example.com"}
|
||||||
res = self.client.get(reverse('alias-list'), data=params)
|
res = self.client.get(reverse("alias-list"), data=params)
|
||||||
self.assertEqual(len(res.data), len(self.aliases))
|
self.assertEqual(len(res.data), len(self.aliases))
|
||||||
|
|
||||||
def test_domain_filter_no_hits(self):
|
def test_domain_filter_no_hits(self):
|
||||||
params = {'domain__name': 'zexample.com'}
|
params = {"domain__name": "zexample.com"}
|
||||||
res = self.client.get(reverse('alias-list'), data=params)
|
res = self.client.get(reverse("alias-list"), data=params)
|
||||||
self.assertEqual(len(res.data), 0)
|
self.assertEqual(len(res.data), 0)
|
||||||
|
|
||||||
def test_regex_filter(self):
|
def test_regex_filter(self):
|
||||||
params = {'source__regex': '^ops-|^ops@'}
|
params = {"source__regex": "^ops-|^ops@"}
|
||||||
res = self.client.get(reverse('alias-list'), data=params)
|
res = self.client.get(reverse("alias-list"), data=params)
|
||||||
self.assertEqual(len(res.data), 1)
|
self.assertEqual(len(res.data), 1)
|
||||||
|
|
||||||
def test_delete_bulk_single(self):
|
def test_delete_bulk_single(self):
|
||||||
data = {'destination': 'ninja@example.com', 'source': 'ding@example.com', 'domain': self.domain.pk}
|
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)
|
Alias.objects.create(source=data["source"], destination=data["destination"], domain=self.domain)
|
||||||
res = self.client.delete(reverse('alias-delete-bulk'), data=[data], format='json')
|
res = self.client.delete(reverse("alias-delete-bulk"), data=[data], format="json")
|
||||||
self.assertEqual(res.status_code, 204, res.data)
|
self.assertEqual(res.status_code, 204, res.data)
|
||||||
+14
-5
@@ -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 rest_framework.routers import DefaultRouter
|
||||||
|
|
||||||
from .views import AliasViewSet, DomainViewSet, UserViewSet
|
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()
|
urlpatterns = [
|
||||||
router.register(r'aliases', AliasViewSet)
|
path("", RedirectView.as_view(pattern_name="api-root")),
|
||||||
router.register(r'domains', DomainViewSet)
|
re_path(r"^api/", include(api_router.urls)),
|
||||||
router.register(r'users', UserViewSet)
|
re_path(r"^admin/", admin.site.urls),
|
||||||
urlpatterns = router.urls
|
re_path(r"^api-auth/", include(rest_framework_urls, namespace="rest_framework")),
|
||||||
|
]
|
||||||
+20
-14
@@ -1,32 +1,36 @@
|
|||||||
import logging
|
import logging
|
||||||
import operator
|
import operator
|
||||||
|
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
|
from rest_framework import status, viewsets
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.mixins import ListModelMixin
|
from rest_framework.mixins import ListModelMixin
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework import status, viewsets
|
|
||||||
from rest_framework.viewsets import GenericViewSet
|
from rest_framework.viewsets import GenericViewSet
|
||||||
|
|
||||||
from dpdapi.filters import AliasRegexFilterBackend
|
from dpdapi.filters import AliasRegexFilterBackend
|
||||||
from dpdapi.models import Alias, Domain, User
|
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__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class AliasViewSet(ListModelMixin, GenericViewSet):
|
class AliasViewSet(ListModelMixin, GenericViewSet):
|
||||||
""" Aliases """
|
"""Aliases"""
|
||||||
|
|
||||||
queryset = Alias.objects.all()
|
queryset = Alias.objects.all()
|
||||||
serializer_class = AliasSerializer
|
serializer_class = AliasSerializer
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
filter_backends = (DjangoFilterBackend, AliasRegexFilterBackend)
|
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):
|
def create_bulk(self, request):
|
||||||
serializer = AliasSerializer(data=request.data, many=True)
|
serializer = AliasSerializer(data=request.data, many=True)
|
||||||
|
|
||||||
@@ -35,7 +39,7 @@ class AliasViewSet(ListModelMixin, GenericViewSet):
|
|||||||
serializer.save()
|
serializer.save()
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||||
|
|
||||||
@action(detail=False, methods=['delete'])
|
@action(detail=False, methods=["delete"])
|
||||||
def delete_bulk(self, request):
|
def delete_bulk(self, request):
|
||||||
serializer = AliasDeleteSerializer(data=request.data, many=True)
|
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)
|
q_list = map(lambda x: Q(**x), serializer.initial_data)
|
||||||
aliases = Alias.objects.filter(reduce(operator.or_, q_list))
|
aliases = Alias.objects.filter(reduce(operator.or_, q_list))
|
||||||
|
|
||||||
initial_set = set(map(lambda x: (x['source'], x['destination'], x['domain']), serializer.initial_data))
|
initial_set = set(map(lambda x: (x["source"], x["destination"], x["domain"]), serializer.initial_data))
|
||||||
deleted_set = set(aliases.values_list('source', 'destination', 'domain'))
|
deleted_set = set(aliases.values_list("source", "destination", "domain"))
|
||||||
diff = initial_set.difference(deleted_set)
|
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()
|
aliases.delete()
|
||||||
|
|
||||||
if diff:
|
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))
|
res = "Aliases '{}' could not be found and were not deleted.".format(", ".join(diff_formatted))
|
||||||
logger.info(res)
|
logger.info(res)
|
||||||
return Response({"result": res}, status=status.HTTP_200_OK)
|
return Response({"result": res}, status=status.HTTP_200_OK)
|
||||||
@@ -61,16 +65,18 @@ class AliasViewSet(ListModelMixin, GenericViewSet):
|
|||||||
|
|
||||||
|
|
||||||
class DomainViewSet(viewsets.ModelViewSet):
|
class DomainViewSet(viewsets.ModelViewSet):
|
||||||
""" Domains """
|
"""Domains"""
|
||||||
|
|
||||||
queryset = Domain.objects.all()
|
queryset = Domain.objects.all()
|
||||||
serializer_class = DomainSerializer
|
serializer_class = DomainSerializer
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
filter_backends = (DjangoFilterBackend,)
|
filter_backends = (DjangoFilterBackend,)
|
||||||
filterset_fields = ('name',)
|
filterset_fields = ("name",)
|
||||||
|
|
||||||
|
|
||||||
class UserViewSet(viewsets.ModelViewSet):
|
class UserViewSet(viewsets.ModelViewSet):
|
||||||
""" Users """
|
"""Users"""
|
||||||
|
|
||||||
queryset = User.objects.all()
|
queryset = User.objects.all()
|
||||||
serializer_class = UserSerializer
|
serializer_class = UserSerializer
|
||||||
permission_classes = [IsAuthenticated]
|
permission_classes = [IsAuthenticated]
|
||||||
@@ -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()
|
||||||
Whitespace-only changes.
@@ -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
|
|
||||||
@@ -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'))
|
|
||||||
]
|
|
||||||
@@ -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()
|
|
||||||
@@ -3,7 +3,7 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
if __name__ == "__main__":
|
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
|
from django.core.management import execute_from_command_line
|
||||||
|
|
||||||
|
|||||||
Generated
+991
@@ -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 = []
|
||||||
@@ -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"
|
||||||
@@ -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.*
|
|
||||||
Reference in new issue
Block a user