mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-05-18 02:19:23 +02:00
Fix linting errors
This commit is contained in:
parent
6263b69c34
commit
64f6d03023
7 changed files with 47 additions and 289 deletions
|
@ -32,4 +32,3 @@ types-requests = "*"
|
|||
django-stubs = "*"
|
||||
django-webtest = "*"
|
||||
types-cachetools = "*"
|
||||
graphviz = ">=0.4"
|
||||
|
|
38
src/Pipfile.lock
generated
38
src/Pipfile.lock
generated
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)})
|
||||
|
|
|
@ -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)
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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"):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue