New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create Remaining Initial Django Models #82
Conversation
a31364c
to
3cd3a27
Compare
Taking a look |
Here's a visualization of this model setup made using: ./scripts/manage graph_models api > graph.dot And using https://dreampuf.github.io/GraphvizOnline/ to convert that to SVG: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great! Nice work.
There may be some State design we can borrow from other projects. Only minor comments here and there. Will take another look once addressed.
rm "$DJANGO_DATA_DIR/$NC_GEOJSON_FILENAME" | ||
fetchDefaultDBData | ||
else | ||
echo "Skipping default django data fetching: file exists." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice attention to developer experience here
# We should populate db with a State so we have ability to | ||
# add upcoming state field on Utility with default FK id | ||
State = apps.get_model('api', 'State') | ||
NC_GEOJSON = json.load(open(os.path.join(settings.BASE_DIR, 'data/NC_BOUNDARY.geojson'), 'r')) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, prefer to open files using with
so the file is closed after use:
with open(os.path.join(settings.BASE_DIR, 'data/NC_BOUNDARY.geojson'), 'r') as f:
NC_GEOSJON = json.load(f)
...
src/django/api/models/state.py
Outdated
id = models.CharField(max_length=2, primary_key=True, unique=True) | ||
name = models.CharField(max_length=127) | ||
boundary = gis_models.PolygonField(geography=True) | ||
options = models.JSONField(blank=True, null=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of django.db.models.JSONField
, prefer django.contrib.postgres.fields.JSONField
, to take advantage of Postgres-native JSON support.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also consider adding help_text
explaining that this JSON dictionary will contain state-specific configuration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Documenting here since this was a separate slack discussion, the postgres JSONField
is deprecated and will lose support in Django 4.0 so we'll move forward with the recommended django.db.models.JSONField
. I'll still update to add help_text
to this field, though.
src/django/api/models/state.py
Outdated
class State(models.Model): | ||
id = models.CharField(max_length=2, primary_key=True, unique=True) | ||
name = models.CharField(max_length=127) | ||
boundary = gis_models.PolygonField(geography=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since "Boundary" is an entity, with "shape" as the polygon field name, let's reuse that here for consistency.
boundary = gis_models.PolygonField(geography=True) | |
shape = gis_models.PolygonField(geography=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, let's use a MultiPolygonField since many states have disconnected polygons in their shape (e.g. Michigan)
src/django/api/models/state.py
Outdated
|
||
|
||
class State(models.Model): | ||
id = models.CharField(max_length=2, primary_key=True, unique=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer using django.contrib.postgres.fields.CICharField
for a case-insensitive field that will match "NC", "nc", and any other combination.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, will implement! Will also get rid of the clean()
method to uppercase that field since that will no longer be needed
|
||
|
||
class ReferenceImage(models.Model): | ||
filename = models.CharField(max_length=100, blank=True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer 255
as the filename length which is more standard.
|
||
class ReferenceImage(models.Model): | ||
filename = models.CharField(max_length=100, blank=True) | ||
boundary = models.ForeignKey(Boundary, on_delete=models.CASCADE) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For on_delete
, prefer models.PROTECT
in most cases, except those where accidental deletion of related data is tolerable. I think in this app, we should use models.PROTECT
in all cases.
92d1397
to
790ea0a
Compare
] | ||
|
||
operations = [ | ||
migrations.RunPython(add_default_state_nc), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another team discussion topic, but I try to always add a reverse operation for RunPython
--at the very least RunPython.noop--because it makes unapplying migrations easier (which does come up in development, when switching branches).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would second a team discussion on this one! I popped RunPython.noop
in for myself when I was reverting some of these migrations for edits but noticed some of our other projects didn't have one so I took it out....but maybe worth bringing back in regardless for our own ease of use?
Taking another look |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👏 Excellent work! This is looking great. Should be good to go after a few small tweaks.
790ea0a
to
5406b69
Compare
Since Utility and State have a many-to-one relationship, with state being a non-nullable FK field on Utility, a default State needed to exist before the creation of the state FK field on Utility. A data migration adding in the NC State row occurs after the State model migration and before the Utility update migration resolves this.
5406b69
to
2b771db
Compare
Thanks, all! Those last changes are all implemented for this next review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 tested, this looks great! Nice work carrying this through the different iterations. There may be some more small tweaks in the future, but this solid base sets us up very well. Great job!
Overview
This creates the initial Django models needed for the different states of submission as well as for the starting user interactions on the frontend, as discussed in the issue comments.
Closes #69
Demo
An ADR is available as part of the PR.
Notes
State
default instance, North Carolina, is created through a data migration that references a GEOJSON file downloaded duringscripts/fetch-data
. I found the state boundary url here, but not sure if there's a better resource for state boundary geojson files? I also includedscripts/fetch-data
as part of the setup script so that the GEOJSON gets setup before running migrations. I didn't see any downsides to this, but not sure if I'm missing context on fetching the municipality data (the original script purpose)?__str__
methods to some of the models where it felt appropriate, but not all. I wasn't sure what language to use to best describe instances like the "approval of the submission of a boundary of a utility" when there wasn't a unique identifier besides the utility, and it seemed to better fit a future task once we better know use case anyways.Submission
,Approval
,Review
, andAnnotation
models into one file. These felt like all related pieces of the submission process that could benefit from being organized together, but curious what others think here.I made theThese fields'max_length
of theSubmission.upload_filename
andReferenceImage.filename
fields match themax_length
of the DjangoFileField
since that seemed like an appropriate reference.max_length
are now255
.Testing Instructions
./scripts/fetch-data
./scripts/update
./scripts/server
State
instance and fields are as expectedUser
andUtility
have newstate
fieldsUtility
'sstate
field defaults to North CarolinaChecklist
fixup!
commits have been squashedCHANGELOG.md
updated with summary of features or fixes, following Keep a Changelog guidelinesREADME.md
updated if necessary to reflect the changes