Fix linting errors

This commit is contained in:
Neil Martinsen-Burrell 2022-11-07 14:01:13 -06:00
parent 6263b69c34
commit 64f6d03023
No known key found for this signature in database
GPG key ID: 6A3C818CC10D0184
7 changed files with 47 additions and 289 deletions

View file

@ -32,4 +32,3 @@ types-requests = "*"
django-stubs = "*"
django-webtest = "*"
types-cachetools = "*"
graphviz = ">=0.4"

38
src/Pipfile.lock generated
View file

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "80ebab4c3aa382d11cc102ff541e23fd3a43e7943eb66ad58b2805304dbaf4fe"
"sha256": "f3c73d2389ee9b1648528a855174d19d20b67f64a2337a660ebeaf613db31488"
},
"pipfile-spec": 6,
"requires": {},
@ -260,7 +260,7 @@
"hashes": [
"sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'",
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.18.2"
},
"gunicorn": {
@ -526,7 +526,7 @@
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.16.0"
},
"sqlparse": {
@ -584,7 +584,7 @@
"sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30",
"sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"
],
"markers": "python_version >= '3.6'",
"markers": "python_full_version >= '3.6.0'",
"version": "==4.11.1"
},
"black": {
@ -683,7 +683,7 @@
"sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd",
"sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"
],
"markers": "python_version >= '3.6'",
"markers": "python_full_version >= '3.6.0'",
"version": "==4.0.9"
},
"gitpython": {
@ -694,20 +694,12 @@
"markers": "python_version >= '3.7'",
"version": "==3.1.29"
},
"graphviz": {
"hashes": [
"sha256:587c58a223b51611c0cf461132da386edd896a029524ca61a1462b880bf97977",
"sha256:8c58f14adaa3b947daf26c19bc1e98c4e0702cdc31cf99153e6f06904d492bf8"
],
"index": "pypi",
"version": "==0.20.1"
},
"mccabe": {
"hashes": [
"sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325",
"sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"
],
"markers": "python_version >= '3.6'",
"markers": "python_full_version >= '3.6.0'",
"version": "==0.7.0"
},
"mypy": {
@ -790,7 +782,7 @@
"sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785",
"sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"
],
"markers": "python_version >= '3.6'",
"markers": "python_full_version >= '3.6.0'",
"version": "==2.9.1"
},
"pyflakes": {
@ -798,7 +790,7 @@
"sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2",
"sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"
],
"markers": "python_version >= '3.6'",
"markers": "python_full_version >= '3.6.0'",
"version": "==2.5.0"
},
"pyyaml": {
@ -844,7 +836,7 @@
"sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174",
"sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"
],
"markers": "python_version >= '3.6'",
"markers": "python_full_version >= '3.6.0'",
"version": "==6.0"
},
"six": {
@ -852,7 +844,7 @@
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.16.0"
},
"smmap": {
@ -860,7 +852,7 @@
"sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94",
"sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"
],
"markers": "python_version >= '3.6'",
"markers": "python_full_version >= '3.6.0'",
"version": "==5.0.0"
},
"soupsieve": {
@ -868,7 +860,7 @@
"sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759",
"sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"
],
"markers": "python_version >= '3.6'",
"markers": "python_full_version >= '3.6.0'",
"version": "==2.3.2.post1"
},
"sqlparse": {
@ -892,7 +884,7 @@
"sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
"sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"
],
"markers": "python_full_version < '3.11.0a7'",
"markers": "python_version < '3.11'",
"version": "==2.0.1"
},
"types-cachetools": {
@ -953,7 +945,7 @@
"sha256:73aae30359291c14fa3b956f8b5ca31960e420c28c1bec002547fb04928cf89b",
"sha256:b64ef5141be559cfade448f044fa45c2260351edcb6a8ef6b7e00c7dcef0c323"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.8.7"
},
"webtest": {
@ -961,7 +953,7 @@
"sha256:2a001a9efa40d2a7e5d9cd8d1527c75f41814eb6afce2c3d207402547b1e5ead",
"sha256:54bd969725838d9861a9fa27f8d971f79d275d94ae255f5c501f53bb6d9929eb"
],
"markers": "python_version >= '3.6' and python_version < '4'",
"markers": "python_version < '4' and python_full_version >= '3.6.0'",
"version": "==3.0.0"
}
}

View file

@ -1,8 +1,6 @@
"""Internal API views"""
import re
from django.core.exceptions import BadRequest
from django.views.decorators.http import require_http_methods
from django.http import JsonResponse
@ -13,22 +11,11 @@ import requests
from cachetools.func import ttl_cache
from registrar.models import Website
DOMAIN_FILE_URL = (
"https://raw.githubusercontent.com/cisagov/dotgov-data/main/current-full.csv"
)
# a domain name is alphanumeric or hyphen, up to 63 characters, doesn't
# begin or end with a hyphen, followed by a TLD of 2-6 alphabetic characters
DOMAIN_REGEX = re.compile(r"^(?!-)[A-Za-z0-9-]{1,63}(?<!-)\.[A-Za-z]{2,6}")
def string_could_be_domain(domain):
"""Return True if the string could be a domain name, otherwise False.
TODO: when we have a Domain class, this could be a classmethod there.
"""
if DOMAIN_REGEX.match(domain):
return True
return False
# this file doesn't change that often, nor is it that big, so cache the result
@ -48,7 +35,7 @@ def _domains():
# get the domain before the first comma
domain = line.split(",", 1)[0]
# sanity-check the string we got from the file here
if string_could_be_domain(domain):
if Website.string_could_be_domain(domain):
# lowercase everything when we put it in domains
domains.add(domain.lower())
return domains
@ -80,7 +67,10 @@ def available(request, domain=""):
"""
# validate that the given domain could be a domain name and fail early if
# not.
if not (string_could_be_domain(domain) or string_could_be_domain(domain + ".gov")):
if not (
Website.string_could_be_domain(domain)
or Website.string_could_be_domain(domain + ".gov")
):
raise BadRequest("Invalid request.")
# a domain is available if it is NOT in the list of current domains
return JsonResponse({"available": not in_domains(domain)})

View file

@ -1,237 +0,0 @@
# -*- coding: utf-8; mode: django -*-
import graphviz
from optparse import make_option
from itertools import chain
from django.core.management.base import BaseCommand
try:
from django.utils.encoding import force_text
_requires_system_checks = True
except ImportError: # Django >= 4.0
from django.utils.encoding import force_str as force_text
from django.core.management.base import ALL_CHECKS
_requires_system_checks = ALL_CHECKS
from django_fsm import FSMFieldMixin, GET_STATE, RETURN_VALUE
try:
from django.db.models import get_apps, get_app, get_models, get_model
NEW_META_API = False
except ImportError:
from django.apps import apps
NEW_META_API = True
from django import VERSION
HAS_ARGPARSE = VERSION >= (1, 10)
def all_fsm_fields_data(model):
if NEW_META_API:
return [(field, model) for field in model._meta.get_fields() if isinstance(field, FSMFieldMixin)]
else:
return [(field, model) for field in model._meta.fields if isinstance(field, FSMFieldMixin)]
def node_name(field, state):
opts = field.model._meta
return "%s.%s.%s.%s" % (opts.app_label, opts.verbose_name.replace(" ", "_"), field.name, state)
def node_label(field, state):
if type(state) == int or (type(state) == bool and hasattr(field, "choices")):
return force_text(dict(field.choices).get(state))
else:
return state
def generate_dot(fields_data):
result = graphviz.Digraph()
for field, model in fields_data:
sources, targets, edges, any_targets, any_except_targets = set(), set(), set(), set(), set()
# dump nodes and edges
for transition in field.get_all_transitions(model):
if transition.source == "*":
any_targets.add((transition.target, transition.name))
elif transition.source == "+":
any_except_targets.add((transition.target, transition.name))
else:
_targets = (
(state for state in transition.target.allowed_states)
if isinstance(transition.target, (GET_STATE, RETURN_VALUE))
else (transition.target,)
)
source_name_pair = (
((state, node_name(field, state)) for state in transition.source.allowed_states)
if isinstance(transition.source, (GET_STATE, RETURN_VALUE))
else ((transition.source, node_name(field, transition.source)),)
)
for source, source_name in source_name_pair:
if transition.on_error:
on_error_name = node_name(field, transition.on_error)
targets.add((on_error_name, node_label(field, transition.on_error)))
edges.add((source_name, on_error_name, (("style", "dotted"),)))
for target in _targets:
add_transition(source, target, transition.name, source_name, field, sources, targets, edges)
targets.update(
set((node_name(field, target), node_label(field, target)) for target, _ in chain(any_targets, any_except_targets))
)
for target, name in any_targets:
target_name = node_name(field, target)
all_nodes = sources | targets
for source_name, label in all_nodes:
sources.add((source_name, label))
edges.add((source_name, target_name, (("label", name),)))
for target, name in any_except_targets:
target_name = node_name(field, target)
all_nodes = sources | targets
all_nodes.remove(((target_name, node_label(field, target))))
for source_name, label in all_nodes:
sources.add((source_name, label))
edges.add((source_name, target_name, (("label", name),)))
# construct subgraph
opts = field.model._meta
subgraph = graphviz.Digraph(
name="cluster_%s_%s_%s" % (opts.app_label, opts.object_name, field.name),
graph_attr={"label": "%s.%s.%s" % (opts.app_label, opts.object_name, field.name)},
)
final_states = targets - sources
for name, label in final_states:
subgraph.node(name, label=label, shape="doublecircle")
for name, label in (sources | targets) - final_states:
subgraph.node(name, label=label, shape="circle")
if field.default: # Adding initial state notation
if label == field.default:
initial_name = node_name(field, "_initial")
subgraph.node(name=initial_name, label="", shape="point")
subgraph.edge(initial_name, name)
for source_name, target_name, attrs in edges:
subgraph.edge(source_name, target_name, **dict(attrs))
result.subgraph(subgraph)
return result
def add_transition(transition_source, transition_target, transition_name, source_name, field, sources, targets, edges):
target_name = node_name(field, transition_target)
sources.add((source_name, node_label(field, transition_source)))
targets.add((target_name, node_label(field, transition_target)))
edges.add((source_name, target_name, (("label", transition_name),)))
def get_graphviz_layouts():
try:
import graphviz
return graphviz.backend.ENGINES
except Exception:
return {"sfdp", "circo", "twopi", "dot", "neato", "fdp", "osage", "patchwork"}
class Command(BaseCommand):
requires_system_checks = _requires_system_checks
if not HAS_ARGPARSE:
option_list = BaseCommand.option_list + (
make_option(
"--output",
"-o",
action="store",
dest="outputfile",
help=(
"Render output file. Type of output dependent on file extensions. " "Use png or jpg to render graph to image."
),
),
# NOQA
make_option(
"--layout",
"-l",
action="store",
dest="layout",
default="dot",
help=("Layout to be used by GraphViz for visualization. " "Layouts: %s." % " ".join(get_graphviz_layouts())),
),
)
args = "[appname[.model[.field]]]"
else:
def add_arguments(self, parser):
parser.add_argument(
"--output",
"-o",
action="store",
dest="outputfile",
help=(
"Render output file. Type of output dependent on file extensions. " "Use png or jpg to render graph to image."
),
)
parser.add_argument(
"--layout",
"-l",
action="store",
dest="layout",
default="dot",
help=("Layout to be used by GraphViz for visualization. " "Layouts: %s." % " ".join(get_graphviz_layouts())),
)
parser.add_argument("args", nargs="*", help=("[appname[.model[.field]]]"))
help = "Creates a GraphViz dot file with transitions for selected fields"
def render_output(self, graph, **options):
filename, format = options["outputfile"].rsplit(".", 1)
graph.engine = options["layout"]
graph.format = format
graph.render(filename)
def handle(self, *args, **options):
fields_data = []
if len(args) != 0:
for arg in args:
field_spec = arg.split(".")
if len(field_spec) == 1:
if NEW_META_API:
app = apps.get_app(field_spec[0])
models = apps.get_models(app)
else:
app = get_app(field_spec[0])
models = get_models(app)
for model in models:
fields_data += all_fsm_fields_data(model)
elif len(field_spec) == 2:
if NEW_META_API:
model = apps.get_model(field_spec[0], field_spec[1])
else:
model = get_model(field_spec[0], field_spec[1])
fields_data += all_fsm_fields_data(model)
elif len(field_spec) == 3:
if NEW_META_API:
model = apps.get_model(field_spec[0], field_spec[1])
else:
model = get_model(field_spec[0], field_spec[1])
fields_data += all_fsm_fields_data(model)
else:
if NEW_META_API:
for model in apps.get_models():
fields_data += all_fsm_fields_data(model)
else:
for app in get_apps():
for model in get_models(app):
fields_data += all_fsm_fields_data(model)
dotdata = generate_dot(fields_data)
if options["outputfile"]:
self.render_output(dotdata, **options)
else:
print(dotdata)

View file

@ -3,7 +3,7 @@
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django_fsm
import django_fsm # type: ignore
class Migration(migrations.Migration):

View file

@ -1,10 +1,10 @@
import re
from django.core.exceptions import ObjectDoesNotExist
from django.contrib.auth.models import AbstractUser
from django.db import models
from django_fsm import FSMField, transition
from ..api.views.available import string_could_be_domain
from django_fsm import FSMField, transition # type: ignore
class User(AbstractUser):
@ -98,6 +98,20 @@ class Website(models.Model):
# enough.
website = models.CharField(max_length=255, null=False, help_text="")
# a domain name is alphanumeric or hyphen, up to 63 characters, doesn't
# begin or end with a hyphen, followed by a TLD of 2-6 alphabetic characters
DOMAIN_REGEX = re.compile(r"^(?!-)[A-Za-z0-9-]{1,63}(?<!-)\.[A-Za-z]{2,6}")
@classmethod
def string_could_be_domain(cls, domain):
"""Return True if the string could be a domain name, otherwise False.
TODO: when we have a Domain class, this could be a classmethod there.
"""
if cls.DOMAIN_REGEX.match(domain):
return True
return False
class Contact(models.Model):
@ -212,7 +226,10 @@ class DomainApplication(TimeStampedModel):
alternative_domains = models.ManyToManyField(Website, related_name="alternatives+")
submitter = models.ForeignKey(
Contact, null=True, related_name="submitted_applications", on_delete=models.PROTECT
Contact,
null=True,
related_name="submitted_applications",
on_delete=models.PROTECT,
)
purpose = models.TextField(null=True, help_text="Purpose of the domain")
@ -230,13 +247,12 @@ class DomainApplication(TimeStampedModel):
)
acknowledged_policy = models.BooleanField(
null=True,
help_text="Acknowledged .gov acceptable use policy"
null=True, help_text="Acknowledged .gov acceptable use policy"
)
def can_submit(self):
"""Return True if this instance can be marked as submitted."""
if not string_could_be_domain(requested_domain):
if not Website.string_could_be_domain(self.requested_domain):
return False
return True
@ -247,5 +263,3 @@ class DomainApplication(TimeStampedModel):
"""Submit an application that is started."""
# don't need to do anything inside this method although we could
pass

View file

@ -3,8 +3,8 @@ from django.db.utils import IntegrityError
from registrar.models import Contact, DomainApplication, User, Website
class TestDomainApplication(TestCase):
class TestDomainApplication(TestCase):
def test_empty_create_fails(self):
"""Can't create a completely empty domain application."""
with self.assertRaisesRegex(IntegrityError, "creator"):