mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-22 10:46:06 +02:00
Add domain application data model
This commit is contained in:
parent
d3b9a993cd
commit
6263b69c34
8 changed files with 799 additions and 80 deletions
|
@ -19,6 +19,7 @@ django-formtools = "*"
|
||||||
django-widget-tweaks = "*"
|
django-widget-tweaks = "*"
|
||||||
cachetools = "*"
|
cachetools = "*"
|
||||||
requests = "*"
|
requests = "*"
|
||||||
|
django-fsm = "*"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
django-debug-toolbar = "*"
|
django-debug-toolbar = "*"
|
||||||
|
@ -31,3 +32,4 @@ types-requests = "*"
|
||||||
django-stubs = "*"
|
django-stubs = "*"
|
||||||
django-webtest = "*"
|
django-webtest = "*"
|
||||||
types-cachetools = "*"
|
types-cachetools = "*"
|
||||||
|
graphviz = ">=0.4"
|
||||||
|
|
174
src/Pipfile.lock
generated
174
src/Pipfile.lock
generated
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "1b7689dae771eaeee047ef75ed1da344ebc9d40fbb9ade689e9dba885e20ec59"
|
"sha256": "80ebab4c3aa382d11cc102ff541e23fd3a43e7943eb66ad58b2805304dbaf4fe"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {},
|
"requires": {},
|
||||||
|
@ -131,35 +131,35 @@
|
||||||
},
|
},
|
||||||
"cryptography": {
|
"cryptography": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0297ffc478bdd237f5ca3a7dc96fc0d315670bfa099c04dc3a4a2172008a405a",
|
"sha256:068147f32fa662c81aebab95c74679b401b12b57494872886eb5c1139250ec5d",
|
||||||
"sha256:10d1f29d6292fc95acb597bacefd5b9e812099d75a6469004fd38ba5471a977f",
|
"sha256:06fc3cc7b6f6cca87bd56ec80a580c88f1da5306f505876a71c8cfa7050257dd",
|
||||||
"sha256:16fa61e7481f4b77ef53991075de29fc5bacb582a1244046d2e8b4bb72ef66d0",
|
"sha256:25c1d1f19729fb09d42e06b4bf9895212292cb27bb50229f5aa64d039ab29146",
|
||||||
"sha256:194044c6b89a2f9f169df475cc167f6157eb9151cc69af8a2a163481d45cc407",
|
"sha256:402852a0aea73833d982cabb6d0c3bb582c15483d29fb7085ef2c42bfa7e38d7",
|
||||||
"sha256:1db3d807a14931fa317f96435695d9ec386be7b84b618cc61cfa5d08b0ae33d7",
|
"sha256:4e269dcd9b102c5a3d72be3c45d8ce20377b8076a43cbed6f660a1afe365e436",
|
||||||
"sha256:3261725c0ef84e7592597606f6583385fed2a5ec3909f43bc475ade9729a41d6",
|
"sha256:5419a127426084933076132d317911e3c6eb77568a1ce23c3ac1e12d111e61e0",
|
||||||
"sha256:3b72c360427889b40f36dc214630e688c2fe03e16c162ef0aa41da7ab1455153",
|
"sha256:554bec92ee7d1e9d10ded2f7e92a5d70c1f74ba9524947c0ba0c850c7b011828",
|
||||||
"sha256:3e3a2599e640927089f932295a9a247fc40a5bdf69b0484532f530471a382750",
|
"sha256:5e89468fbd2fcd733b5899333bc54d0d06c80e04cd23d8c6f3e0542358c6060b",
|
||||||
"sha256:3fc26e22840b77326a764ceb5f02ca2d342305fba08f002a8c1f139540cdfaad",
|
"sha256:65535bc550b70bd6271984d9863a37741352b4aad6fb1b3344a54e6950249b55",
|
||||||
"sha256:5067ee7f2bce36b11d0e334abcd1ccf8c541fc0bbdaf57cdd511fdee53e879b6",
|
"sha256:6ab9516b85bebe7aa83f309bacc5f44a61eeb90d0b4ec125d2d003ce41932d36",
|
||||||
"sha256:52e7bee800ec869b4031093875279f1ff2ed12c1e2f74923e8f49c916afd1d3b",
|
"sha256:6addc3b6d593cd980989261dc1cce38263c76954d758c3c94de51f1e010c9a50",
|
||||||
"sha256:64760ba5331e3f1794d0bcaabc0d0c39e8c60bf67d09c93dc0e54189dfd7cfe5",
|
"sha256:728f2694fa743a996d7784a6194da430f197d5c58e2f4e278612b359f455e4a2",
|
||||||
"sha256:765fa194a0f3372d83005ab83ab35d7c5526c4e22951e46059b8ac678b44fa5a",
|
"sha256:785e4056b5a8b28f05a533fab69febf5004458e20dad7e2e13a3120d8ecec75a",
|
||||||
"sha256:79473cf8a5cbc471979bd9378c9f425384980fcf2ab6534b18ed7d0d9843987d",
|
"sha256:78cf5eefac2b52c10398a42765bfa981ce2372cbc0457e6bf9658f41ec3c41d8",
|
||||||
"sha256:896dd3a66959d3a5ddcfc140a53391f69ff1e8f25d93f0e2e7830c6de90ceb9d",
|
"sha256:7f836217000342d448e1c9a342e9163149e45d5b5eca76a30e84503a5a96cab0",
|
||||||
"sha256:89ed49784ba88c221756ff4d4755dbc03b3c8d2c5103f6d6b4f83a0fb1e85294",
|
"sha256:8d41a46251bf0634e21fac50ffd643216ccecfaf3701a063257fe0b2be1b6548",
|
||||||
"sha256:ac7e48f7e7261207d750fa7e55eac2d45f720027d5703cd9007e9b37bbb59ac0",
|
"sha256:984fe150f350a3c91e84de405fe49e688aa6092b3525f407a18b9646f6612320",
|
||||||
"sha256:ad7353f6ddf285aeadfaf79e5a6829110106ff8189391704c1d8801aa0bae45a",
|
"sha256:9b24bcff7853ed18a63cfb0c2b008936a9554af24af2fb146e16d8e1aed75748",
|
||||||
"sha256:b0163a849b6f315bf52815e238bc2b2346604413fa7c1601eea84bcddb5fb9ac",
|
"sha256:b1b35d9d3a65542ed2e9d90115dfd16bbc027b3f07ee3304fc83580f26e43249",
|
||||||
"sha256:b6c9b706316d7b5a137c35e14f4103e2115b088c412140fdbd5f87c73284df61",
|
"sha256:b1b52c9e5f8aa2b802d48bd693190341fae201ea51c7a167d69fc48b60e8a959",
|
||||||
"sha256:c2e5856248a416767322c8668ef1845ad46ee62629266f84a8f007a317141013",
|
"sha256:bbf203f1a814007ce24bd4d51362991d5cb90ba0c177a9c08825f2cc304d871f",
|
||||||
"sha256:ca9f6784ea96b55ff41708b92c3f6aeaebde4c560308e5fbbd3173fbc466e94e",
|
"sha256:be243c7e2bfcf6cc4cb350c0d5cdf15ca6383bbcb2a8ef51d3c9411a9d4386f0",
|
||||||
"sha256:d1a5bd52d684e49a36582193e0b89ff267704cd4025abefb9e26803adeb3e5fb",
|
"sha256:bfbe6ee19615b07a98b1d2287d6a6073f734735b49ee45b11324d85efc4d5cbd",
|
||||||
"sha256:d3971e2749a723e9084dd507584e2a2761f78ad2c638aa31e80bc7a15c9db4f9",
|
"sha256:c46837ea467ed1efea562bbeb543994c2d1f6e800785bd5a2c98bc096f5cb220",
|
||||||
"sha256:d4ef6cc305394ed669d4d9eebf10d3a101059bdcf2669c366ec1d14e4fb227bd",
|
"sha256:dfb4f4dd568de1b6af9f4cda334adf7d72cf5bc052516e1b2608b683375dd95c",
|
||||||
"sha256:d9e69ae01f99abe6ad646947bba8941e896cb3aa805be2597a0400e0764b5818"
|
"sha256:ed7b00096790213e09eb11c97cc6e2b757f15f3d2f85833cd2d3ec3fe37c1722"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.6'",
|
"markers": "python_version >= '3.6'",
|
||||||
"version": "==38.0.1"
|
"version": "==38.0.3"
|
||||||
},
|
},
|
||||||
"defusedxml": {
|
"defusedxml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -185,11 +185,11 @@
|
||||||
},
|
},
|
||||||
"django": {
|
"django": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:26dc24f99c8956374a054bcbf58aab8dc0cad2e6ac82b0fe036b752c00eee793",
|
"sha256:678bbfc8604eb246ed54e2063f0765f13b321a50526bdc8cb1f943eda7fa31f1",
|
||||||
"sha256:b8d843714810ab88d59344507d4447be8b2cf12a49031363b6eed9f1b9b2280f"
|
"sha256:6b1de6886cae14c7c44d188f580f8ba8da05750f544c80ae5ad43375ab293cd5"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==4.1.2"
|
"version": "==4.1.3"
|
||||||
},
|
},
|
||||||
"django-allow-cidr": {
|
"django-allow-cidr": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -222,6 +222,14 @@
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==2.4"
|
"version": "==2.4"
|
||||||
},
|
},
|
||||||
|
"django-fsm": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:e2c02cbf273fb9691aa9a907c29990afdd21a4adea09c5640344c93fbe03f8d9",
|
||||||
|
"sha256:fd9f8de9f33188e50f876ce53908fbd7289e5031a44ffdb97d43909e56699ef8"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==2.8.1"
|
||||||
|
},
|
||||||
"django-widget-tweaks": {
|
"django-widget-tweaks": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:9bfc5c705684754a83cc81da328b39ad1b80f32bd0f4340e2a810cbab4b0c00e",
|
"sha256:9bfc5c705684754a83cc81da328b39ad1b80f32bd0f4340e2a810cbab4b0c00e",
|
||||||
|
@ -507,11 +515,11 @@
|
||||||
},
|
},
|
||||||
"setuptools": {
|
"setuptools": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:512e5536220e38146176efb833d4a62aa726b7bbff82cfbc8ba9eaa3996e0b17",
|
"sha256:d0b9a8433464d5800cbe05094acf5c6d52a91bfac9b52bcfc4d41382be5d5d31",
|
||||||
"sha256:f62ea9da9ed6289bfe868cd6845968a2c854d1427f8548d52cae02a42b4f0356"
|
"sha256:e197a19aa8ec9722928f2206f8de752def0e4c9fc6953527360d1c36d94ddb2f"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==65.5.0"
|
"version": "==65.5.1"
|
||||||
},
|
},
|
||||||
"six": {
|
"six": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -624,11 +632,11 @@
|
||||||
},
|
},
|
||||||
"django": {
|
"django": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:26dc24f99c8956374a054bcbf58aab8dc0cad2e6ac82b0fe036b752c00eee793",
|
"sha256:678bbfc8604eb246ed54e2063f0765f13b321a50526bdc8cb1f943eda7fa31f1",
|
||||||
"sha256:b8d843714810ab88d59344507d4447be8b2cf12a49031363b6eed9f1b9b2280f"
|
"sha256:6b1de6886cae14c7c44d188f580f8ba8da05750f544c80ae5ad43375ab293cd5"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==4.1.2"
|
"version": "==4.1.3"
|
||||||
},
|
},
|
||||||
"django-debug-toolbar": {
|
"django-debug-toolbar": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -640,19 +648,19 @@
|
||||||
},
|
},
|
||||||
"django-stubs": {
|
"django-stubs": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0dff8ec0ba3abe046450b3d8a29ce9e72629893d2c1ef679189cc2bfdb6d2f64",
|
"sha256:424fdd1935f859a802365056f9ccf4db12d1d93a5ab3de6d5633dddba0c5fc76",
|
||||||
"sha256:ea8b35d0da49f7b2ee99a79125f1943e033431dd114726d6643cc35de619230e"
|
"sha256:eaecc1fc71532c1148f0c9687556651d880165476d7629bf318ff86a903a150c"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==1.12.0"
|
"version": "==1.13.0"
|
||||||
},
|
},
|
||||||
"django-stubs-ext": {
|
"django-stubs-ext": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:9bd7418376ab00b7f88d6d56be9fece85bfa0c7c348ac621155fa4d7a91146f2",
|
"sha256:4fd8cdbc68d1a421f21bb7e0d9e76d50f6a4b504d350ba786405daf536e90c21",
|
||||||
"sha256:c5d8db53d29c756e7e3d0820a5a079a43bc38d8fab0e1b8bd5df2f3366c54b5a"
|
"sha256:d729fbc7fe8970a7e26b35956c35b48502516f011d523c0577bdfb02ed956284"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.6'",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==0.5.0"
|
"version": "==0.7.0"
|
||||||
},
|
},
|
||||||
"django-webtest": {
|
"django-webtest": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -686,6 +694,14 @@
|
||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==3.1.29"
|
"version": "==3.1.29"
|
||||||
},
|
},
|
||||||
|
"graphviz": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:587c58a223b51611c0cf461132da386edd896a029524ca61a1462b880bf97977",
|
||||||
|
"sha256:8c58f14adaa3b947daf26c19bc1e98c4e0702cdc31cf99153e6f06904d492bf8"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==0.20.1"
|
||||||
|
},
|
||||||
"mccabe": {
|
"mccabe": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325",
|
"sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325",
|
||||||
|
@ -696,33 +712,39 @@
|
||||||
},
|
},
|
||||||
"mypy": {
|
"mypy": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:1021c241e8b6e1ca5a47e4d52601274ac078a89845cfde66c6d5f769819ffa1d",
|
"sha256:0680389c34284287fe00e82fc8bccdea9aff318f7e7d55b90d967a13a9606013",
|
||||||
"sha256:14d53cdd4cf93765aa747a7399f0961a365bcddf7855d9cef6306fa41de01c24",
|
"sha256:1767830da2d1afa4e62b684647af0ff79b401f004d7fa08bc5b0ce2d45bcd5ec",
|
||||||
"sha256:175f292f649a3af7082fe36620369ffc4661a71005aa9f8297ea473df5772046",
|
"sha256:1ee5f99817ee70254e7eb5cf97c1b11dda29c6893d846c8b07bce449184e9466",
|
||||||
"sha256:26ae64555d480ad4b32a267d10cab7aec92ff44de35a7cd95b2b7cb8e64ebe3e",
|
"sha256:262c543ef24deb10470a3c1c254bb986714e2b6b1a67d66daf836a548a9f316c",
|
||||||
"sha256:41fd1cf9bc0e1c19b9af13a6580ccb66c381a5ee2cf63ee5ebab747a4badeba3",
|
"sha256:269f0dfb6463b8780333310ff4b5134425157ef0d2b1d614015adaf6d6a7eabd",
|
||||||
"sha256:5085e6f442003fa915aeb0a46d4da58128da69325d8213b4b35cc7054090aed5",
|
"sha256:2a3150d409609a775c8cb65dbe305c4edd7fe576c22ea79d77d1454acd9aeda8",
|
||||||
"sha256:58f27ebafe726a8e5ccb58d896451dd9a662a511a3188ff6a8a6a919142ecc20",
|
"sha256:2b6f85c2ad378e3224e017904a051b26660087b3b76490d533b7344f1546d3ff",
|
||||||
"sha256:6389af3e204975d6658de4fb8ac16f58c14e1bacc6142fee86d1b5b26aa52bda",
|
"sha256:3227f14fe943524f5794679156488f18bf8d34bfecd4623cf76bc55958d229c5",
|
||||||
"sha256:724d36be56444f569c20a629d1d4ee0cb0ad666078d59bb84f8f887952511ca1",
|
"sha256:3ff201a0c6d3ea029d73b1648943387d75aa052491365b101f6edd5570d018ea",
|
||||||
"sha256:75838c649290d83a2b83a88288c1eb60fe7a05b36d46cbea9d22efc790002146",
|
"sha256:46897755f944176fbc504178422a5a2875bbf3f7436727374724842c0987b5af",
|
||||||
"sha256:7b35ce03a289480d6544aac85fa3674f493f323d80ea7226410ed065cd46f206",
|
"sha256:47a9955214615108c3480a500cfda8513a0b1cd3c09a1ed42764ca0dd7b931dd",
|
||||||
"sha256:85f7a343542dc8b1ed0a888cdd34dca56462654ef23aa673907305b260b3d746",
|
"sha256:49082382f571c3186ce9ea0bd627cb1345d4da8d44a8377870f4442401f0a706",
|
||||||
"sha256:86ebe67adf4d021b28c3f547da6aa2cce660b57f0432617af2cca932d4d378a6",
|
"sha256:4a8a6c10f4c63fbf6ad6c03eba22c9331b3946a4cec97f008e9ffb4d3b31e8e2",
|
||||||
"sha256:8ee8c2472e96beb1045e9081de8e92f295b89ac10c4109afdf3a23ad6e644f3e",
|
"sha256:6826d9c4d85bbf6d68cb279b561de6a4d8d778ca8e9ab2d00ee768ab501a9852",
|
||||||
"sha256:91781eff1f3f2607519c8b0e8518aad8498af1419e8442d5d0afb108059881fc",
|
"sha256:72382cb609142dba3f04140d016c94b4092bc7b4d98ca718740dc989e5271b8d",
|
||||||
"sha256:a692a8e7d07abe5f4b2dd32d731812a0175626a90a223d4b58f10f458747dd8a",
|
"sha256:7da0005e47975287a92b43276e460ac1831af3d23032c34e67d003388a0ce8d0",
|
||||||
"sha256:a705a93670c8b74769496280d2fe6cd59961506c64f329bb179970ff1d24f9f8",
|
"sha256:8798c8ed83aa809f053abff08664bdca056038f5a02af3660de00b7290b64c47",
|
||||||
"sha256:c6e564f035d25c99fd2b863e13049744d96bd1947e3d3d2f16f5828864506763",
|
"sha256:8f1940325a8ed460ba03d19ab83742260fa9534804c317224e5d4e5aa588e2d6",
|
||||||
"sha256:cebca7fd333f90b61b3ef7f217ff75ce2e287482206ef4a8b18f32b49927b1a2",
|
"sha256:8f694d6d09a460b117dccb6857dda269188e3437c880d7b60fa0014fa872d1e9",
|
||||||
"sha256:d6af646bd46f10d53834a8e8983e130e47d8ab2d4b7a97363e35b24e1d588947",
|
"sha256:9b8f4a8213b1fd4b751e26b59ae0e0c12896568d7e805861035c7a15ed6dc9eb",
|
||||||
"sha256:e7aeaa763c7ab86d5b66ff27f68493d672e44c8099af636d433a7f3fa5596d40",
|
"sha256:9d851c09b981a65d9d283a8ccb5b1d0b698e580493416a10942ef1a04b19fd37",
|
||||||
"sha256:eaa97b9ddd1dd9901a22a879491dbb951b5dec75c3b90032e2baa7336777363b",
|
"sha256:aaf1be63e0207d7d17be942dcf9a6b641745581fe6c64df9a38deb562a7dbafa",
|
||||||
"sha256:eb7a068e503be3543c4bd329c994103874fa543c1727ba5288393c21d912d795",
|
"sha256:aba38e3dd66bdbafbbfe9c6e79637841928ea4c79b32e334099463c17b0d90ef",
|
||||||
"sha256:f793e3dd95e166b66d50e7b63e69e58e88643d80a3dcc3bcd81368e0478b089c"
|
"sha256:b08541a06eed35b543ae1a6b301590eb61826a1eb099417676ddc5a42aa151c5",
|
||||||
|
"sha256:be88d665e76b452c26fb2bdc3d54555c01226fba062b004ede780b190a50f9db",
|
||||||
|
"sha256:c76c769c46a1e6062a84837badcb2a7b0cdb153d68601a61f60739c37d41cc74",
|
||||||
|
"sha256:cc6019808580565040cd2a561b593d7c3c646badd7e580e07d875eb1bf35c695",
|
||||||
|
"sha256:cd2dd3730ba894ec2a2082cc703fbf3e95a08479f7be84912e3131fc68809d46",
|
||||||
|
"sha256:d555aa7f44cecb7ea3c0ac69d58b1a5afb92caa017285a8e9c4efbf0518b61b4",
|
||||||
|
"sha256:d847dd23540e2912d9667602271e5ebf25e5788e7da46da5ffd98e7872616e8e"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==0.982"
|
"version": "==0.990"
|
||||||
},
|
},
|
||||||
"mypy-extensions": {
|
"mypy-extensions": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -757,11 +779,11 @@
|
||||||
},
|
},
|
||||||
"platformdirs": {
|
"platformdirs": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788",
|
"sha256:0cb405749187a194f444c25c82ef7225232f11564721eabffc6ec70df83b11cb",
|
||||||
"sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"
|
"sha256:6e52c21afff35cb659c6e52d8b4d61b9bd544557180440538f255d9382c8cbe0"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_version >= '3.7'",
|
||||||
"version": "==2.5.2"
|
"version": "==2.5.3"
|
||||||
},
|
},
|
||||||
"pycodestyle": {
|
"pycodestyle": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -870,7 +892,7 @@
|
||||||
"sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
|
"sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
|
||||||
"sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"
|
"sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"
|
||||||
],
|
],
|
||||||
"markers": "python_version >= '3.7'",
|
"markers": "python_full_version < '3.11.0a7'",
|
||||||
"version": "==2.0.1"
|
"version": "==2.0.1"
|
||||||
},
|
},
|
||||||
"types-cachetools": {
|
"types-cachetools": {
|
||||||
|
@ -883,10 +905,10 @@
|
||||||
},
|
},
|
||||||
"types-pytz": {
|
"types-pytz": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:0c163b15d3e598e6cc7074a99ca9ec72b25dc1b446acc133b827667af0b7b09a",
|
"sha256:bea605ce5d5a5d52a8e1afd7656c9b42476e18a0f888de6be91587355313ddf4",
|
||||||
"sha256:a8e1fe6a1b270fbfaf2553b20ad0f1316707cc320e596da903bb17d7373fed2d"
|
"sha256:d078196374d1277e9f9984d49373ea043cf2c64d5d5c491fbc86c258557bd46f"
|
||||||
],
|
],
|
||||||
"version": "==2022.5.0.0"
|
"version": "==2022.6.0.1"
|
||||||
},
|
},
|
||||||
"types-pyyaml": {
|
"types-pyyaml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
|
|
@ -90,6 +90,7 @@ INSTALLED_APPS = [
|
||||||
"registrar",
|
"registrar",
|
||||||
# Our internal API application
|
# Our internal API application
|
||||||
"api",
|
"api",
|
||||||
|
"django_fsm",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Middleware are routines for processing web requests.
|
# Middleware are routines for processing web requests.
|
||||||
|
|
237
src/registrar/management/commands/graph_transitions.py
Normal file
237
src/registrar/management/commands/graph_transitions.py
Normal file
|
@ -0,0 +1,237 @@
|
||||||
|
# -*- 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)
|
|
@ -0,0 +1,243 @@
|
||||||
|
# Generated by Django 4.1.3 on 2022-11-07 19:39
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django_fsm
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("registrar", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Contact",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("first_name", models.TextField(help_text="First name", null=True)),
|
||||||
|
("middle_name", models.TextField(help_text="Middle name", null=True)),
|
||||||
|
("last_name", models.TextField(help_text="Last name", null=True)),
|
||||||
|
("title", models.TextField(help_text="Title", null=True)),
|
||||||
|
("email", models.TextField(help_text="Email", null=True)),
|
||||||
|
("phone", models.TextField(help_text="Phone", null=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Website",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("website", models.CharField(max_length=255)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="DomainApplication",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
verbose_name="ID",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("created_at", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("updated_at", models.DateTimeField(auto_now=True)),
|
||||||
|
(
|
||||||
|
"status",
|
||||||
|
django_fsm.FSMField(
|
||||||
|
choices=[
|
||||||
|
("started", "started"),
|
||||||
|
("submitted", "submitted"),
|
||||||
|
("investigating", "investigating"),
|
||||||
|
("approved", "approved"),
|
||||||
|
],
|
||||||
|
default="started",
|
||||||
|
max_length=50,
|
||||||
|
protected=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"organization_type",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("federal", "a federal agency"),
|
||||||
|
("interstate", "an organization of two or more states"),
|
||||||
|
(
|
||||||
|
"state_or_territory",
|
||||||
|
"one of the 50 U.S. states, the District of Columbia, American Samoa, Guam, Northern Mariana Islands, Puerto Rico, or the U.S. Virgin Islands",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"tribal",
|
||||||
|
"a tribal government recognized by the federal or state government",
|
||||||
|
),
|
||||||
|
("county", "a county, parish, or borough"),
|
||||||
|
("city", "a city, town, township, village, etc."),
|
||||||
|
(
|
||||||
|
"special_district",
|
||||||
|
"an independent organization within a single state",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
help_text="Type of Organization",
|
||||||
|
max_length=255,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"federal_branch",
|
||||||
|
models.CharField(
|
||||||
|
choices=[
|
||||||
|
("Executive", "Executive"),
|
||||||
|
("Judicial", "Judicial"),
|
||||||
|
("Legislative", "Legislative"),
|
||||||
|
],
|
||||||
|
help_text="Branch of federal government",
|
||||||
|
max_length=50,
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"is_election_office",
|
||||||
|
models.BooleanField(
|
||||||
|
help_text="Is your ogranization an election office?", null=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"organization_name",
|
||||||
|
models.TextField(help_text="Organization name", null=True),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"street_address",
|
||||||
|
models.TextField(help_text="Street Address", null=True),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"unit_type",
|
||||||
|
models.CharField(help_text="Unit type", max_length=15, null=True),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"unit_number",
|
||||||
|
models.CharField(
|
||||||
|
help_text="Unit number", max_length=255, null=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"state_territory",
|
||||||
|
models.CharField(
|
||||||
|
help_text="State/Territory", max_length=2, null=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"zip_code",
|
||||||
|
models.CharField(help_text="ZIP code", max_length=10, null=True),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"purpose",
|
||||||
|
models.TextField(help_text="Purpose of the domain", null=True),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"security_email",
|
||||||
|
models.CharField(
|
||||||
|
help_text="Security email for public use",
|
||||||
|
max_length=320,
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"anything_else",
|
||||||
|
models.TextField(
|
||||||
|
help_text="Anything else we should know?", null=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"acknowledged_policy",
|
||||||
|
models.BooleanField(
|
||||||
|
help_text="Acknowledged .gov acceptable use policy", null=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"alternative_domains",
|
||||||
|
models.ManyToManyField(
|
||||||
|
related_name="alternatives+", to="registrar.website"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"authorizing_official",
|
||||||
|
models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
related_name="authorizing_official",
|
||||||
|
to="registrar.contact",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"creator",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
related_name="applications_created",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"current_websites",
|
||||||
|
models.ManyToManyField(
|
||||||
|
related_name="current+", to="registrar.website"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"investigator",
|
||||||
|
models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="applications_investigating",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"other_contacts",
|
||||||
|
models.ManyToManyField(
|
||||||
|
related_name="contact_applications", to="registrar.contact"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"requested_domain",
|
||||||
|
models.ForeignKey(
|
||||||
|
help_text="The requested domain",
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
related_name="requested+",
|
||||||
|
to="registrar.website",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"submitter",
|
||||||
|
models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.PROTECT,
|
||||||
|
related_name="submitted_applications",
|
||||||
|
to="registrar.contact",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"abstract": False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,3 +1,3 @@
|
||||||
from .models import User, UserProfile
|
from .models import User, UserProfile, Contact, Website, DomainApplication
|
||||||
|
|
||||||
__all__ = ["User", "UserProfile"]
|
__all__ = ["User", "UserProfile", "Contact", "Website", "DomainApplication"]
|
||||||
|
|
|
@ -2,6 +2,10 @@ from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.contrib.auth.models import AbstractUser
|
from django.contrib.auth.models import AbstractUser
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
from django_fsm import FSMField, transition
|
||||||
|
|
||||||
|
from ..api.views.available import string_could_be_domain
|
||||||
|
|
||||||
|
|
||||||
class User(AbstractUser):
|
class User(AbstractUser):
|
||||||
"""
|
"""
|
||||||
|
@ -56,7 +60,7 @@ class AddressModel(models.Model):
|
||||||
# don't put anything else here, it will be ignored
|
# don't put anything else here, it will be ignored
|
||||||
|
|
||||||
|
|
||||||
class ContactModel(models.Model):
|
class ContactInfo(models.Model):
|
||||||
"""
|
"""
|
||||||
An abstract base model that provides common fields
|
An abstract base model that provides common fields
|
||||||
for contact information.
|
for contact information.
|
||||||
|
@ -71,7 +75,7 @@ class ContactModel(models.Model):
|
||||||
# don't put anything else here, it will be ignored
|
# don't put anything else here, it will be ignored
|
||||||
|
|
||||||
|
|
||||||
class UserProfile(TimeStampedModel, ContactModel, AddressModel):
|
class UserProfile(TimeStampedModel, ContactInfo, AddressModel):
|
||||||
user = models.OneToOneField(User, null=True, on_delete=models.CASCADE)
|
user = models.OneToOneField(User, null=True, on_delete=models.CASCADE)
|
||||||
display_name = models.TextField()
|
display_name = models.TextField()
|
||||||
|
|
||||||
|
@ -83,3 +87,165 @@ class UserProfile(TimeStampedModel, ContactModel, AddressModel):
|
||||||
return self.user.username
|
return self.user.username
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
return "No username"
|
return "No username"
|
||||||
|
|
||||||
|
|
||||||
|
class Website(models.Model):
|
||||||
|
|
||||||
|
"""Keep domain names in their own table so that applications can refer to
|
||||||
|
many of them."""
|
||||||
|
|
||||||
|
# domain names have strictly limited lengths, 255 characters is more than
|
||||||
|
# enough.
|
||||||
|
website = models.CharField(max_length=255, null=False, help_text="")
|
||||||
|
|
||||||
|
|
||||||
|
class Contact(models.Model):
|
||||||
|
|
||||||
|
"""Contact information follows a similar pattern for each contact."""
|
||||||
|
|
||||||
|
first_name = models.TextField(null=True, help_text="First name")
|
||||||
|
middle_name = models.TextField(null=True, help_text="Middle name")
|
||||||
|
last_name = models.TextField(null=True, help_text="Last name")
|
||||||
|
title = models.TextField(null=True, help_text="Title")
|
||||||
|
email = models.TextField(null=True, help_text="Email")
|
||||||
|
phone = models.TextField(null=True, help_text="Phone")
|
||||||
|
|
||||||
|
|
||||||
|
class DomainApplication(TimeStampedModel):
|
||||||
|
|
||||||
|
STARTED = "started"
|
||||||
|
SUBMITTED = "submitted"
|
||||||
|
INVESTIGATING = "investigating"
|
||||||
|
APPROVED = "approved"
|
||||||
|
STATUS_CHOICES = [
|
||||||
|
(STARTED, STARTED),
|
||||||
|
(SUBMITTED, SUBMITTED),
|
||||||
|
(INVESTIGATING, INVESTIGATING),
|
||||||
|
(APPROVED, APPROVED),
|
||||||
|
]
|
||||||
|
status = FSMField(
|
||||||
|
choices=STATUS_CHOICES, # possible states as an array of constants
|
||||||
|
default=STARTED, # sensible default
|
||||||
|
protected=True, # cannot change state directly, must use methods!
|
||||||
|
)
|
||||||
|
creator = models.ForeignKey(
|
||||||
|
User, on_delete=models.PROTECT, related_name="applications_created"
|
||||||
|
)
|
||||||
|
investigator = models.ForeignKey(
|
||||||
|
User,
|
||||||
|
null=True,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
related_name="applications_investigating",
|
||||||
|
)
|
||||||
|
|
||||||
|
# data fields from the initial form
|
||||||
|
|
||||||
|
FEDERAL = "federal"
|
||||||
|
INTERSTATE = "interstate"
|
||||||
|
STATE_OR_TERRITORY = "state_or_territory"
|
||||||
|
TRIBAL = "tribal"
|
||||||
|
COUNTY = "county"
|
||||||
|
CITY = "city"
|
||||||
|
SPECIAL_DISTRICT = "special_district"
|
||||||
|
ORGANIZATION_CHOICES = [
|
||||||
|
(FEDERAL, "a federal agency"),
|
||||||
|
(INTERSTATE, "an organization of two or more states"),
|
||||||
|
(
|
||||||
|
STATE_OR_TERRITORY,
|
||||||
|
"one of the 50 U.S. states, the District of "
|
||||||
|
"Columbia, American Samoa, Guam, Northern Mariana Islands, "
|
||||||
|
"Puerto Rico, or the U.S. Virgin Islands",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
TRIBAL,
|
||||||
|
"a tribal government recognized by the federal or " "state government",
|
||||||
|
),
|
||||||
|
(COUNTY, "a county, parish, or borough"),
|
||||||
|
(CITY, "a city, town, township, village, etc."),
|
||||||
|
(SPECIAL_DISTRICT, "an independent organization within a single state"),
|
||||||
|
]
|
||||||
|
organization_type = models.CharField(
|
||||||
|
max_length=255, choices=ORGANIZATION_CHOICES, help_text="Type of Organization"
|
||||||
|
)
|
||||||
|
|
||||||
|
EXECUTIVE = "Executive"
|
||||||
|
JUDICIAL = "Judicial"
|
||||||
|
LEGISLATIVE = "Legislative"
|
||||||
|
BRANCH_CHOICES = [(x, x) for x in (EXECUTIVE, JUDICIAL, LEGISLATIVE)]
|
||||||
|
federal_branch = models.CharField(
|
||||||
|
max_length=50,
|
||||||
|
choices=BRANCH_CHOICES,
|
||||||
|
null=True,
|
||||||
|
help_text="Branch of federal government",
|
||||||
|
)
|
||||||
|
|
||||||
|
is_election_office = models.BooleanField(
|
||||||
|
null=True, help_text="Is your ogranization an election office?"
|
||||||
|
)
|
||||||
|
|
||||||
|
organization_name = models.TextField(null=True, help_text="Organization name")
|
||||||
|
street_address = models.TextField(null=True, help_text="Street Address")
|
||||||
|
unit_type = models.CharField(max_length=15, null=True, help_text="Unit type")
|
||||||
|
unit_number = models.CharField(max_length=255, null=True, help_text="Unit number")
|
||||||
|
state_territory = models.CharField(
|
||||||
|
max_length=2, null=True, help_text="State/Territory"
|
||||||
|
)
|
||||||
|
zip_code = models.CharField(max_length=10, null=True, help_text="ZIP code")
|
||||||
|
|
||||||
|
authorizing_official = models.ForeignKey(
|
||||||
|
Contact,
|
||||||
|
null=True,
|
||||||
|
related_name="authorizing_official",
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
)
|
||||||
|
|
||||||
|
# "+" means no reverse relation to lookup applications from Website
|
||||||
|
current_websites = models.ManyToManyField(Website, related_name="current+")
|
||||||
|
|
||||||
|
requested_domain = models.ForeignKey(
|
||||||
|
Website,
|
||||||
|
null=True,
|
||||||
|
help_text="The requested domain",
|
||||||
|
related_name="requested+",
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
)
|
||||||
|
alternative_domains = models.ManyToManyField(Website, related_name="alternatives+")
|
||||||
|
|
||||||
|
submitter = models.ForeignKey(
|
||||||
|
Contact, null=True, related_name="submitted_applications", on_delete=models.PROTECT
|
||||||
|
)
|
||||||
|
|
||||||
|
purpose = models.TextField(null=True, help_text="Purpose of the domain")
|
||||||
|
|
||||||
|
other_contacts = models.ManyToManyField(
|
||||||
|
Contact, related_name="contact_applications"
|
||||||
|
)
|
||||||
|
|
||||||
|
security_email = models.CharField(
|
||||||
|
max_length=320, null=True, help_text="Security email for public use"
|
||||||
|
)
|
||||||
|
|
||||||
|
anything_else = models.TextField(
|
||||||
|
null=True, help_text="Anything else we should know?"
|
||||||
|
)
|
||||||
|
|
||||||
|
acknowledged_policy = models.BooleanField(
|
||||||
|
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):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
@transition(
|
||||||
|
field="status", source=STARTED, target=SUBMITTED, conditions=[can_submit]
|
||||||
|
)
|
||||||
|
def submit(self):
|
||||||
|
"""Submit an application that is started."""
|
||||||
|
# don't need to do anything inside this method although we could
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
48
src/registrar/tests/test_models.py
Normal file
48
src/registrar/tests/test_models.py
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.db.utils import IntegrityError
|
||||||
|
|
||||||
|
from registrar.models import Contact, DomainApplication, User, Website
|
||||||
|
|
||||||
|
class TestDomainApplication(TestCase):
|
||||||
|
|
||||||
|
def test_empty_create_fails(self):
|
||||||
|
"""Can't create a completely empty domain application."""
|
||||||
|
with self.assertRaisesRegex(IntegrityError, "creator"):
|
||||||
|
DomainApplication.objects.create()
|
||||||
|
|
||||||
|
def test_minimal_create(self):
|
||||||
|
"""Can create with just a creator."""
|
||||||
|
user, _ = User.objects.get_or_create()
|
||||||
|
application = DomainApplication.objects.create(creator=user)
|
||||||
|
self.assertEquals(application.status, DomainApplication.STARTED)
|
||||||
|
|
||||||
|
def test_full_create(self):
|
||||||
|
"""Can create with all fields."""
|
||||||
|
user, _ = User.objects.get_or_create()
|
||||||
|
contact = Contact.objects.create()
|
||||||
|
com_website, _ = Website.objects.get_or_create(website="igorville.com")
|
||||||
|
gov_website, _ = Website.objects.get_or_create(website="igorville.gov")
|
||||||
|
application = DomainApplication.objects.create(
|
||||||
|
creator=user,
|
||||||
|
investigator=user,
|
||||||
|
organization_type=DomainApplication.FEDERAL,
|
||||||
|
federal_branch=DomainApplication.EXECUTIVE,
|
||||||
|
is_election_office=False,
|
||||||
|
organization_name="Test",
|
||||||
|
street_address="100 Main St.",
|
||||||
|
unit_type="APT",
|
||||||
|
unit_number="1A",
|
||||||
|
state_territory="CA",
|
||||||
|
zip_code="12345-6789",
|
||||||
|
authorizing_official=contact,
|
||||||
|
requested_domain=gov_website,
|
||||||
|
submitter=contact,
|
||||||
|
purpose="Igorville rules!",
|
||||||
|
security_email="security@igorville.gov",
|
||||||
|
anything_else="All of Igorville loves the dotgov program.",
|
||||||
|
acknowledged_policy=True,
|
||||||
|
)
|
||||||
|
application.current_websites.add(com_website)
|
||||||
|
application.alternative_domains.add(gov_website)
|
||||||
|
application.other_contacts.add(contact)
|
||||||
|
application.save()
|
Loading…
Add table
Add a link
Reference in a new issue