Compare commits
No commits in common. "a65b1574dba558ee2b2115e977322a086758204f" and "37f3ed2506de71a8ecbeb29ce594d71880409af9" have entirely different histories.
a65b1574db
...
37f3ed2506
19 changed files with 93 additions and 295 deletions
|
@ -1,9 +1,6 @@
|
||||||
Changelog
|
Changelog
|
||||||
=========
|
=========
|
||||||
|
|
||||||
* :feature:`-` add REST API to retrieve and set user information as admin
|
|
||||||
* :feature:`-` add support model for offline account reset codes in new help
|
|
||||||
app
|
|
||||||
* :support:`-` remove unused PowerDNS support tables from domains app
|
* :support:`-` remove unused PowerDNS support tables from domains app
|
||||||
* :feature:`-` add impersonation support for superusers
|
* :feature:`-` add impersonation support for superusers
|
||||||
* :support:`-` remove django-braces dependency
|
* :support:`-` remove django-braces dependency
|
||||||
|
|
|
@ -7,10 +7,20 @@ from unittest.mock import patch
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
from domains.models import HostingDomain, MailDomain
|
from domains.models import (
|
||||||
|
DNSComment,
|
||||||
|
DNSCryptoKey,
|
||||||
|
DNSDomain,
|
||||||
|
DNSDomainMetadata,
|
||||||
|
DNSRecord,
|
||||||
|
DNSSupermaster,
|
||||||
|
DNSTSIGKey,
|
||||||
|
HostingDomain,
|
||||||
|
MailDomain,
|
||||||
|
)
|
||||||
from hostingpackages.models import CustomerHostingPackage, HostingPackageTemplate
|
from hostingpackages.models import CustomerHostingPackage, HostingPackageTemplate
|
||||||
|
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
TEST_USER = "test"
|
TEST_USER = "test"
|
||||||
|
@ -67,3 +77,49 @@ class HostingDomainTest(TestCase):
|
||||||
def test___str__(self):
|
def test___str__(self):
|
||||||
hostingdomain = HostingDomain(domain="test")
|
hostingdomain = HostingDomain(domain="test")
|
||||||
self.assertEqual(str(hostingdomain), "test")
|
self.assertEqual(str(hostingdomain), "test")
|
||||||
|
|
||||||
|
|
||||||
|
class DNSDomainTest(TestCase):
|
||||||
|
def test___str__(self):
|
||||||
|
dnsdomain = DNSDomain(domain="test")
|
||||||
|
self.assertEqual(str(dnsdomain), "test")
|
||||||
|
|
||||||
|
|
||||||
|
class DNSRecordTest(TestCase):
|
||||||
|
def test___str__(self):
|
||||||
|
dnsrecord = DNSRecord(name="localhost", recordtype="A", content="127.0.0.1")
|
||||||
|
self.assertEqual(str(dnsrecord), "localhost IN A 127.0.0.1")
|
||||||
|
|
||||||
|
|
||||||
|
class DNSSupermasterTest(TestCase):
|
||||||
|
def test___str__(self):
|
||||||
|
dnssupermaster = DNSSupermaster(ip="127.0.0.1", nameserver="dns.example.org")
|
||||||
|
self.assertEqual(str(dnssupermaster), "127.0.0.1 dns.example.org")
|
||||||
|
|
||||||
|
|
||||||
|
class DNSCommentTest(TestCase):
|
||||||
|
def test___str__(self):
|
||||||
|
dnscomment = DNSComment(name="localhost", commenttype="A", comment="good stuff")
|
||||||
|
self.assertEqual(str(dnscomment), "localhost IN A: good stuff")
|
||||||
|
|
||||||
|
|
||||||
|
class DNSDomainMetadataTest(TestCase):
|
||||||
|
def test___str__(self):
|
||||||
|
dnsdomain = DNSDomain(domain="test")
|
||||||
|
dnsdomainmetadata = DNSDomainMetadata(
|
||||||
|
domain=dnsdomain, kind="SOA-EDIT", content="INCEPTION"
|
||||||
|
)
|
||||||
|
self.assertEqual(str(dnsdomainmetadata), "test SOA-EDIT INCEPTION")
|
||||||
|
|
||||||
|
|
||||||
|
class DNSCryptoKeyTest(TestCase):
|
||||||
|
def test___str__(self):
|
||||||
|
dnsdomain = DNSDomain(domain="test")
|
||||||
|
dnscryptokey = DNSCryptoKey(domain=dnsdomain, content="testvalue")
|
||||||
|
self.assertEqual(str(dnscryptokey), "test testvalue")
|
||||||
|
|
||||||
|
|
||||||
|
class DNSTSIGKeyTest(TestCase):
|
||||||
|
def test___str__(self):
|
||||||
|
dnstsigkey = DNSTSIGKey(name="testkey", algorithm="hmac-md5", secret="dummykey")
|
||||||
|
self.assertEqual(str(dnstsigkey), "testkey hmac-md5 XXXX")
|
||||||
|
|
|
@ -207,8 +207,6 @@ DJANGO_APPS = (
|
||||||
"django.contrib.flatpages",
|
"django.contrib.flatpages",
|
||||||
"crispy_forms",
|
"crispy_forms",
|
||||||
"impersonate",
|
"impersonate",
|
||||||
"rest_framework",
|
|
||||||
"rest_framework.authtoken",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
ALLAUTH_APPS = (
|
ALLAUTH_APPS = (
|
||||||
|
@ -234,7 +232,6 @@ LOCAL_APPS = (
|
||||||
"userdbs",
|
"userdbs",
|
||||||
"hostingpackages",
|
"hostingpackages",
|
||||||
"websites",
|
"websites",
|
||||||
"help",
|
|
||||||
"contact_form",
|
"contact_form",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -266,17 +263,6 @@ CRISPY_TEMPLATE_PACK = "bootstrap3"
|
||||||
# ######### END CRISPY_FORMS CONFIGURATION
|
# ######### END CRISPY_FORMS CONFIGURATION
|
||||||
|
|
||||||
|
|
||||||
# ######### REST FRAMEWORK CONFIGURATION
|
|
||||||
REST_FRAMEWORK = {
|
|
||||||
"DEFAULT_AUTHENTICATION_CLASSES": [
|
|
||||||
"rest_framework.authentication.BasicAuthentication",
|
|
||||||
"rest_framework.authentication.SessionAuthentication",
|
|
||||||
"rest_framework.authentication.TokenAuthentication",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
# ######### END REST FRAMEWORK CONFIGURATION
|
|
||||||
|
|
||||||
|
|
||||||
# ######### LOGGING CONFIGURATION
|
# ######### LOGGING CONFIGURATION
|
||||||
# See: https://docs.djangoproject.com/en/dev/ref/settings/#logging
|
# See: https://docs.djangoproject.com/en/dev/ref/settings/#logging
|
||||||
# A sample logging configuration. The only tangible logging
|
# A sample logging configuration. The only tangible logging
|
||||||
|
@ -418,8 +404,23 @@ if GVA_ENVIRONMENT == "local":
|
||||||
dict(
|
dict(
|
||||||
[
|
[
|
||||||
(key, {"handlers": ["console"], "level": "DEBUG", "propagate": True})
|
(key, {"handlers": ["console"], "level": "DEBUG", "propagate": True})
|
||||||
for key in LOCAL_APPS
|
for key in [
|
||||||
],
|
"dashboard",
|
||||||
|
"domains",
|
||||||
|
"fileservertasks",
|
||||||
|
"gvacommon",
|
||||||
|
"gvawebcore",
|
||||||
|
"hostingpackages",
|
||||||
|
"ldaptasks",
|
||||||
|
"managemails",
|
||||||
|
"mysqltasks",
|
||||||
|
"osusers",
|
||||||
|
"pgsqltasks",
|
||||||
|
"taskresults",
|
||||||
|
"userdbs",
|
||||||
|
"websites",
|
||||||
|
]
|
||||||
|
]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
elif GVA_ENVIRONMENT == "test":
|
elif GVA_ENVIRONMENT == "test":
|
||||||
|
@ -438,7 +439,22 @@ elif GVA_ENVIRONMENT == "test":
|
||||||
dict(
|
dict(
|
||||||
[
|
[
|
||||||
(key, {"handlers": ["console"], "level": "ERROR", "propagate": True})
|
(key, {"handlers": ["console"], "level": "ERROR", "propagate": True})
|
||||||
for key in LOCAL_APPS
|
for key in [
|
||||||
|
"dashboard",
|
||||||
|
"domains",
|
||||||
|
"fileservertasks",
|
||||||
|
"gvacommon",
|
||||||
|
"gvawebcore",
|
||||||
|
"hostingpackages",
|
||||||
|
"ldaptasks",
|
||||||
|
"managemails",
|
||||||
|
"mysqltasks",
|
||||||
|
"osusers",
|
||||||
|
"pgsqltasks",
|
||||||
|
"taskresults",
|
||||||
|
"userdbs",
|
||||||
|
"websites",
|
||||||
|
]
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,20 +6,11 @@ from django.contrib import admin
|
||||||
from django.contrib.flatpages import views
|
from django.contrib.flatpages import views
|
||||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||||
from django.urls import path, re_path
|
from django.urls import path, re_path
|
||||||
from rest_framework import routers
|
|
||||||
|
|
||||||
from help import views as help_views
|
|
||||||
|
|
||||||
admin.autodiscover()
|
admin.autodiscover()
|
||||||
|
|
||||||
router = routers.DefaultRouter()
|
|
||||||
router.register(r"users", help_views.UserViewSet)
|
|
||||||
router.register(r"help-users", help_views.HelpUserViewSet)
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
re_path(r"", include("dashboard.urls")),
|
re_path(r"", include("dashboard.urls")),
|
||||||
path("api/", include(router.urls)),
|
|
||||||
path("api-auth/", include("rest_framework.urls", namespace="rest_framework")),
|
|
||||||
re_path(r"^admin/", admin.site.urls),
|
re_path(r"^admin/", admin.site.urls),
|
||||||
re_path(r"^impersonate/", include("impersonate.urls")),
|
re_path(r"^impersonate/", include("impersonate.urls")),
|
||||||
re_path(r"^accounts/", include("allauth.urls")),
|
re_path(r"^accounts/", include("allauth.urls")),
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
from django.contrib import admin
|
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
from help.models import HelpUser
|
|
||||||
|
|
||||||
User = get_user_model()
|
|
||||||
|
|
||||||
|
|
||||||
class HelpUserInline(admin.StackedInline):
|
|
||||||
model = HelpUser
|
|
||||||
can_delete = False
|
|
||||||
readonly_fields = ("offline_account_code",)
|
|
||||||
|
|
||||||
|
|
||||||
class UserAdmin(BaseUserAdmin):
|
|
||||||
inlines = (HelpUserInline,)
|
|
||||||
|
|
||||||
|
|
||||||
admin.site.unregister(User)
|
|
||||||
admin.site.register(User, UserAdmin)
|
|
|
@ -1,8 +0,0 @@
|
||||||
from django.apps import AppConfig
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
|
|
||||||
class HelpConfig(AppConfig):
|
|
||||||
default_auto_field = "django.db.models.BigAutoField"
|
|
||||||
name = "help"
|
|
||||||
verbose_name = _("User self help")
|
|
|
@ -1,17 +0,0 @@
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from django.core.management import BaseCommand
|
|
||||||
|
|
||||||
from help.models import HelpUser
|
|
||||||
|
|
||||||
User = get_user_model()
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
help = "Populate help user information for existing users"
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
for user in User.objects.filter(helpuser=None):
|
|
||||||
help_user = HelpUser.objects.create(user_id=user.id, email_address=user.email)
|
|
||||||
help_user.generate_offline_account_code()
|
|
||||||
help_user.save()
|
|
||||||
self.stdout.write(f"created offline account code {help_user.offline_account_code} for {user}.")
|
|
|
@ -1,29 +0,0 @@
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from django.core.management import BaseCommand, CommandError
|
|
||||||
|
|
||||||
from help.models import HelpUser
|
|
||||||
|
|
||||||
User = get_user_model()
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
help = "Reset offline account reset code for existing users"
|
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
|
||||||
parser.add_argument("users", nargs='+', type=str)
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
for name in options["users"]:
|
|
||||||
try:
|
|
||||||
user = User.objects.get(username=name)
|
|
||||||
except User.DoesNotExist:
|
|
||||||
raise CommandError(f'User {name} does not exist')
|
|
||||||
|
|
||||||
help_user = user.helpuser
|
|
||||||
if help_user is None:
|
|
||||||
help_user = HelpUser.objects.create(email_address=user.email)
|
|
||||||
|
|
||||||
help_user.generate_offline_account_code()
|
|
||||||
help_user.save()
|
|
||||||
|
|
||||||
self.stdout.write(f"generated new offline account reset code {help_user.offline_account_code} for {name}")
|
|
|
@ -1,55 +0,0 @@
|
||||||
# Generated by Django 3.2.18 on 2023-04-16 09:32
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name="HelpUser",
|
|
||||||
fields=[
|
|
||||||
(
|
|
||||||
"id",
|
|
||||||
models.BigAutoField(
|
|
||||||
auto_created=True,
|
|
||||||
primary_key=True,
|
|
||||||
serialize=False,
|
|
||||||
verbose_name="ID",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"email_address",
|
|
||||||
models.EmailField(
|
|
||||||
help_text="Contact email address", max_length=254
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"postal_address",
|
|
||||||
models.TextField(blank=True, help_text="Contact postal address"),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"offline_account_code",
|
|
||||||
models.CharField(
|
|
||||||
default="",
|
|
||||||
help_text="Offline account reset code",
|
|
||||||
max_length=36,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"user",
|
|
||||||
models.OneToOneField(
|
|
||||||
on_delete=django.db.models.deletion.CASCADE,
|
|
||||||
to=settings.AUTH_USER_MODEL,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,17 +0,0 @@
|
||||||
import uuid
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import models
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
|
|
||||||
|
|
||||||
class HelpUser(models.Model):
|
|
||||||
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
|
||||||
email_address = models.EmailField(help_text=_("Contact email address"))
|
|
||||||
postal_address = models.TextField(help_text=_("Contact postal address"), blank=True)
|
|
||||||
offline_account_code = models.CharField(
|
|
||||||
help_text=_("Offline account reset code"), max_length=36, default=""
|
|
||||||
)
|
|
||||||
|
|
||||||
def generate_offline_account_code(self):
|
|
||||||
self.offline_account_code = str(uuid.uuid4())
|
|
|
@ -1,30 +0,0 @@
|
||||||
"""
|
|
||||||
Serializers for the REST API
|
|
||||||
"""
|
|
||||||
|
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from rest_framework import serializers
|
|
||||||
|
|
||||||
from help.models import HelpUser
|
|
||||||
|
|
||||||
User = get_user_model()
|
|
||||||
|
|
||||||
|
|
||||||
class UserSerializer(serializers.HyperlinkedModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = User
|
|
||||||
fields = ["url", "username", "helpuser"]
|
|
||||||
read_only_fields = ["username", "helpuser"]
|
|
||||||
|
|
||||||
|
|
||||||
class HelpUserSerializer(serializers.HyperlinkedModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = HelpUser
|
|
||||||
fields = [
|
|
||||||
"url",
|
|
||||||
"user",
|
|
||||||
"email_address",
|
|
||||||
"postal_address",
|
|
||||||
"offline_account_code",
|
|
||||||
]
|
|
||||||
read_only_fields = ["user", "offline_account_code"]
|
|
|
@ -1,3 +0,0 @@
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
|
@ -1,29 +0,0 @@
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from rest_framework import permissions, viewsets
|
|
||||||
|
|
||||||
from help.models import HelpUser
|
|
||||||
from help.serializers import HelpUserSerializer, UserSerializer
|
|
||||||
|
|
||||||
User = get_user_model()
|
|
||||||
|
|
||||||
|
|
||||||
class UserViewSet(viewsets.ReadOnlyModelViewSet):
|
|
||||||
"""
|
|
||||||
API endpoint that allows users to be viewed or edited.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
queryset = User.objects.all().order_by("-username")
|
|
||||||
serializer_class = UserSerializer
|
|
||||||
permission_classes = [permissions.IsAdminUser]
|
|
||||||
|
|
||||||
|
|
||||||
class HelpUserViewSet(viewsets.ModelViewSet):
|
|
||||||
"""
|
|
||||||
API endpoint that allows user help profile to be viewed or edited.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
queryset = HelpUser.objects.all().order_by("-user__username")
|
|
||||||
serializer_class = HelpUserSerializer
|
|
||||||
permission_classes = [permissions.IsAdminUser]
|
|
51
poetry.lock
generated
51
poetry.lock
generated
|
@ -666,21 +666,6 @@ files = [
|
||||||
django = ">=3.2.4"
|
django = ">=3.2.4"
|
||||||
sqlparse = ">=0.2"
|
sqlparse = ">=0.2"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "django-filter"
|
|
||||||
version = "23.1"
|
|
||||||
description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically."
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
files = [
|
|
||||||
{file = "django-filter-23.1.tar.gz", hash = "sha256:dee5dcf2cea4d7f767e271b6d01f767fce7500676d5e5dc58dac8154000b87df"},
|
|
||||||
{file = "django_filter-23.1-py3-none-any.whl", hash = "sha256:e3c52ad83c32fb5882125105efb5fea2a1d6a85e7dc64b04ef52edbf14451b6c"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
Django = ">=3.2"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "django-impersonate"
|
name = "django-impersonate"
|
||||||
version = "1.9.1"
|
version = "1.9.1"
|
||||||
|
@ -707,22 +692,6 @@ files = [
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
Django = ">=3.2"
|
Django = ">=3.2"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "djangorestframework"
|
|
||||||
version = "3.14.0"
|
|
||||||
description = "Web APIs for Django, made easy."
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.6"
|
|
||||||
files = [
|
|
||||||
{file = "djangorestframework-3.14.0-py3-none-any.whl", hash = "sha256:eb63f58c9f218e1a7d064d17a70751f528ed4e1d35547fdade9aaf4cd103fd08"},
|
|
||||||
{file = "djangorestframework-3.14.0.tar.gz", hash = "sha256:579a333e6256b09489cbe0a067e66abe55c6595d8926be6b99423786334350c8"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
django = ">=3.0"
|
|
||||||
pytz = "*"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "docutils"
|
name = "docutils"
|
||||||
version = "0.19"
|
version = "0.19"
|
||||||
|
@ -881,24 +850,6 @@ sqs = ["boto3 (>=1.9.12)", "pycurl (>=7.44.1,<7.45.0)", "urllib3 (>=1.26.7)"]
|
||||||
yaml = ["PyYAML (>=3.10)"]
|
yaml = ["PyYAML (>=3.10)"]
|
||||||
zookeeper = ["kazoo (>=1.3.1)"]
|
zookeeper = ["kazoo (>=1.3.1)"]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "markdown"
|
|
||||||
version = "3.4.3"
|
|
||||||
description = "Python implementation of John Gruber's Markdown."
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
files = [
|
|
||||||
{file = "Markdown-3.4.3-py3-none-any.whl", hash = "sha256:065fd4df22da73a625f14890dd77eb8040edcbd68794bcd35943be14490608b2"},
|
|
||||||
{file = "Markdown-3.4.3.tar.gz", hash = "sha256:8bf101198e004dc93e84a12a7395e31aac6a9c9942848ae1d99b9d72cf9b3520"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""}
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
testing = ["coverage", "pyyaml"]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "markupsafe"
|
name = "markupsafe"
|
||||||
version = "2.1.2"
|
version = "2.1.2"
|
||||||
|
@ -1802,4 +1753,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.7"
|
python-versions = "^3.7"
|
||||||
content-hash = "12e95bb19c0dc9d4b1388423e1007628e37e5e13a217de01b27bb34b20d5ac34"
|
content-hash = "dd56e0233689448f08dfcae943871bf9d72c05ad7bfd326c69f9ecb33ea8a461"
|
||||||
|
|
|
@ -20,9 +20,6 @@ passlib = "^1.7.4"
|
||||||
redis = "^4.5.1"
|
redis = "^4.5.1"
|
||||||
requests-oauthlib = "^1.3.1"
|
requests-oauthlib = "^1.3.1"
|
||||||
django-impersonate = "^1.9.1"
|
django-impersonate = "^1.9.1"
|
||||||
djangorestframework = "^3.14.0"
|
|
||||||
markdown = "^3.4.3"
|
|
||||||
django-filter = "^23.1"
|
|
||||||
|
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
|
Loading…
Reference in a new issue