Compare commits
	
		
			4 commits
		
	
	
		
			37f3ed2506
			...
			a65b1574db
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| a65b1574db | |||
| 9731d4e793 | |||
| f9ea88cd24 | |||
| 4b7e311c62 | 
					 19 changed files with 295 additions and 93 deletions
				
			
		|  | @ -1,6 +1,9 @@ | |||
| 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 | ||||
| * :feature:`-` add impersonation support for superusers | ||||
| * :support:`-` remove django-braces dependency | ||||
|  |  | |||
|  | @ -7,19 +7,9 @@ from unittest.mock import patch | |||
| from django.test import TestCase | ||||
| from django.contrib.auth import get_user_model | ||||
| 
 | ||||
| from domains.models import ( | ||||
|     DNSComment, | ||||
|     DNSCryptoKey, | ||||
|     DNSDomain, | ||||
|     DNSDomainMetadata, | ||||
|     DNSRecord, | ||||
|     DNSSupermaster, | ||||
|     DNSTSIGKey, | ||||
|     HostingDomain, | ||||
|     MailDomain, | ||||
| ) | ||||
| from hostingpackages.models import CustomerHostingPackage, HostingPackageTemplate | ||||
| from domains.models import HostingDomain, MailDomain | ||||
| 
 | ||||
| from hostingpackages.models import CustomerHostingPackage, HostingPackageTemplate | ||||
| 
 | ||||
| User = get_user_model() | ||||
| 
 | ||||
|  | @ -77,49 +67,3 @@ class HostingDomainTest(TestCase): | |||
|     def test___str__(self): | ||||
|         hostingdomain = HostingDomain(domain="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,6 +207,8 @@ DJANGO_APPS = ( | |||
|     "django.contrib.flatpages", | ||||
|     "crispy_forms", | ||||
|     "impersonate", | ||||
|     "rest_framework", | ||||
|     "rest_framework.authtoken", | ||||
| ) | ||||
| 
 | ||||
| ALLAUTH_APPS = ( | ||||
|  | @ -232,6 +234,7 @@ LOCAL_APPS = ( | |||
|     "userdbs", | ||||
|     "hostingpackages", | ||||
|     "websites", | ||||
|     "help", | ||||
|     "contact_form", | ||||
| ) | ||||
| 
 | ||||
|  | @ -263,6 +266,17 @@ CRISPY_TEMPLATE_PACK = "bootstrap3" | |||
| # ######### 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 | ||||
| # See: https://docs.djangoproject.com/en/dev/ref/settings/#logging | ||||
| # A sample logging configuration. The only tangible logging | ||||
|  | @ -404,23 +418,8 @@ if GVA_ENVIRONMENT == "local": | |||
|         dict( | ||||
|             [ | ||||
|                 (key, {"handlers": ["console"], "level": "DEBUG", "propagate": True}) | ||||
|                 for key in [ | ||||
|                 "dashboard", | ||||
|                 "domains", | ||||
|                 "fileservertasks", | ||||
|                 "gvacommon", | ||||
|                 "gvawebcore", | ||||
|                 "hostingpackages", | ||||
|                 "ldaptasks", | ||||
|                 "managemails", | ||||
|                 "mysqltasks", | ||||
|                 "osusers", | ||||
|                 "pgsqltasks", | ||||
|                 "taskresults", | ||||
|                 "userdbs", | ||||
|                 "websites", | ||||
|             ] | ||||
|             ] | ||||
|                 for key in LOCAL_APPS | ||||
|             ], | ||||
|         ) | ||||
|     ) | ||||
| elif GVA_ENVIRONMENT == "test": | ||||
|  | @ -439,22 +438,7 @@ elif GVA_ENVIRONMENT == "test": | |||
|         dict( | ||||
|             [ | ||||
|                 (key, {"handlers": ["console"], "level": "ERROR", "propagate": True}) | ||||
|                 for key in [ | ||||
|                 "dashboard", | ||||
|                 "domains", | ||||
|                 "fileservertasks", | ||||
|                 "gvacommon", | ||||
|                 "gvawebcore", | ||||
|                 "hostingpackages", | ||||
|                 "ldaptasks", | ||||
|                 "managemails", | ||||
|                 "mysqltasks", | ||||
|                 "osusers", | ||||
|                 "pgsqltasks", | ||||
|                 "taskresults", | ||||
|                 "userdbs", | ||||
|                 "websites", | ||||
|             ] | ||||
|                 for key in LOCAL_APPS | ||||
|             ] | ||||
|         ) | ||||
|     ) | ||||
|  |  | |||
|  | @ -6,11 +6,20 @@ from django.contrib import admin | |||
| from django.contrib.flatpages import views | ||||
| from django.contrib.staticfiles.urls import staticfiles_urlpatterns | ||||
| from django.urls import path, re_path | ||||
| from rest_framework import routers | ||||
| 
 | ||||
| from help import views as help_views | ||||
| 
 | ||||
| admin.autodiscover() | ||||
| 
 | ||||
| router = routers.DefaultRouter() | ||||
| router.register(r"users", help_views.UserViewSet) | ||||
| router.register(r"help-users", help_views.HelpUserViewSet) | ||||
| 
 | ||||
| urlpatterns = [ | ||||
|     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"^impersonate/", include("impersonate.urls")), | ||||
|     re_path(r"^accounts/", include("allauth.urls")), | ||||
|  |  | |||
							
								
								
									
										0
									
								
								gnuviechadmin/help/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								gnuviechadmin/help/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
								
								
									
										22
									
								
								gnuviechadmin/help/admin.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								gnuviechadmin/help/admin.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,22 @@ | |||
| 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) | ||||
							
								
								
									
										8
									
								
								gnuviechadmin/help/apps.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								gnuviechadmin/help/apps.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | |||
| 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") | ||||
							
								
								
									
										0
									
								
								gnuviechadmin/help/management/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								gnuviechadmin/help/management/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
								
								
									
										0
									
								
								gnuviechadmin/help/management/commands/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								gnuviechadmin/help/management/commands/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
								
								
									
										17
									
								
								gnuviechadmin/help/management/commands/populate.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								gnuviechadmin/help/management/commands/populate.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| 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}.") | ||||
							
								
								
									
										29
									
								
								gnuviechadmin/help/management/commands/reset_offline_code.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								gnuviechadmin/help/management/commands/reset_offline_code.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | |||
| 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}") | ||||
|  | @ -0,0 +1,55 @@ | |||
| # 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, | ||||
|                     ), | ||||
|                 ), | ||||
|             ], | ||||
|         ), | ||||
|     ] | ||||
							
								
								
									
										0
									
								
								gnuviechadmin/help/migrations/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								gnuviechadmin/help/migrations/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
								
								
									
										17
									
								
								gnuviechadmin/help/models.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								gnuviechadmin/help/models.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| 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()) | ||||
							
								
								
									
										30
									
								
								gnuviechadmin/help/serializers.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								gnuviechadmin/help/serializers.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,30 @@ | |||
| """ | ||||
| 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"] | ||||
							
								
								
									
										3
									
								
								gnuviechadmin/help/tests.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								gnuviechadmin/help/tests.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| from django.test import TestCase | ||||
| 
 | ||||
| # Create your tests here. | ||||
							
								
								
									
										29
									
								
								gnuviechadmin/help/views.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								gnuviechadmin/help/views.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | |||
| 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,6 +666,21 @@ files = [ | |||
| django = ">=3.2.4" | ||||
| 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]] | ||||
| name = "django-impersonate" | ||||
| version = "1.9.1" | ||||
|  | @ -692,6 +707,22 @@ files = [ | |||
| [package.dependencies] | ||||
| 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]] | ||||
| name = "docutils" | ||||
| version = "0.19" | ||||
|  | @ -850,6 +881,24 @@ sqs = ["boto3 (>=1.9.12)", "pycurl (>=7.44.1,<7.45.0)", "urllib3 (>=1.26.7)"] | |||
| yaml = ["PyYAML (>=3.10)"] | ||||
| 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]] | ||||
| name = "markupsafe" | ||||
| version = "2.1.2" | ||||
|  | @ -1753,4 +1802,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more | |||
| [metadata] | ||||
| lock-version = "2.0" | ||||
| python-versions = "^3.7" | ||||
| content-hash = "dd56e0233689448f08dfcae943871bf9d72c05ad7bfd326c69f9ecb33ea8a461" | ||||
| content-hash = "12e95bb19c0dc9d4b1388423e1007628e37e5e13a217de01b27bb34b20d5ac34" | ||||
|  |  | |||
|  | @ -20,6 +20,9 @@ passlib = "^1.7.4" | |||
| redis = "^4.5.1" | ||||
| requests-oauthlib = "^1.3.1" | ||||
| django-impersonate = "^1.9.1" | ||||
| djangorestframework = "^3.14.0" | ||||
| markdown = "^3.4.3" | ||||
| django-filter = "^23.1" | ||||
| 
 | ||||
| 
 | ||||
| [tool.poetry.group.dev.dependencies] | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue