Skip to content

Commit 70655df

Browse files
authored
Merge pull request #164 from cmu-delphi/development
Merge development into staging
2 parents 97334a6 + 7b88f25 commit 70655df

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1485
-270
lines changed

.github/workflows/build-and-deploy.yaml

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ jobs:
66
runs-on: ubuntu-latest
77
# CI/CD will run on these branches
88
if: >
9-
github.ref == 'refs/heads/master' ||
9+
github.ref == 'refs/heads/main' ||
10+
github.ref == 'refs/heads/staging' ||
1011
github.ref == 'refs/heads/development'
1112
strategy:
1213
matrix:
@@ -27,7 +28,7 @@ jobs:
2728
baseRef="${GITHUB_REF#*/}"
2829
baseRef="${baseRef#*/}"
2930
case "${baseRef}" in
30-
master)
31+
main)
3132
image_tag="latest"
3233
;;
3334
*)

README.md

+16-3
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,14 @@ Open `http://localhost:8000` to view it in the browser
8282
Though probably not necessary in most cases, if you want to test/modify/emulate how this will run in production you can:
8383

8484
- In `.env` set:
85-
```
85+
86+
```shell
8687
DEBUG = 'False'
8788
```
89+
8890
- Modify the app container's command in `docker-compose.yaml` to run:
89-
```
91+
92+
```shell
9093
gunicorn signal_documentation.wsgi:application --bind 0.0.0.0:8000"
9194
9295
*(Essentially you'll replace just the last line of the command, switching out the "runserver" line)
@@ -96,6 +99,10 @@ Open `http://localhost` to view it in the browser. In this usage your request wi
9699
97100
The primary use case for this will be when making changes to the Nginx container image that runs in production and hosts the static file content, or also if making changes to the Gunicorn config.
98101
102+
Additionally, though again not required for local development, you can also specify an env var of `MAIN_PAGE = $name`, and the app will be served at `http://localhost:8000/$name` (if running in debug mode), or if you've set `DEBUG = 'False'` to run it in Nginx/production mode at `http://localhost/$name/`. Note the ending slash when in Nginx/production mode _and_ using the `MAIN_PAGE` env var.
103+
104+
The primary use case is so that we have flexibility to serve the application at something other than the "bare" URL, though doing this is not necessary for local development.
105+
99106
Changes of this sort should be carefully evaluated as they may require interaction with systems managed by devops folks.
100107
101108
## [Django admin](https://docs.djangoproject.com/en/4.1/ref/contrib/admin/) web interface (user should be `is_staff` or `is_superuser`)
@@ -200,9 +207,15 @@ Each environment is essentially a bunch of different services all governed by `d
200207
201208
### Basic workflow
202209
203-
- A PR merged to either `development` or `master` will trigger CI to build container images that are then tagged (based on the branch name and ":latest" respectively) and stored in our GitHub Packages container image repository.
210+
- A PR merged to either `development`, `staging`, or `main` will trigger CI to build container images that are then tagged with the branch name (or ":latest", in the cast of `main`), and stored in our GitHub Packages container image repository.
204211
- CI triggers a webhook that tells the host systems to pull and run new container images and restart any services that have been updated.
205212
213+
As a developer, your path to getting changes into production should be something like this:
214+
215+
- Source your working branch from `development`, do work, PR and merge when complete
216+
- PR and merge to `staging` in order to get your changes deployed to https://staging.delphi.cmu.edu/signals for review
217+
- PR and merge to `main` to go to production
218+
206219
**IMPORTANT!** - The CI/CD process uses Docker Compose to build the specific container images that will be used in external environments. Success of the the build-and-deploy workflow is dependent on constructed services in `docker-compose.yaml`. If considering making changes there, please have a PR reviewed by devops folks :pray: :pray: :pray:
207220
208221
### Control of the deployed environment

docker-compose.yaml

+1-9
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,7 @@ services:
3131
restart: on-failure
3232
command: sh -c "python3 /usr/src/signal_documentation/src/manage.py migrate --noinput &&
3333
python3 /usr/src/signal_documentation/src/manage.py collectstatic --noinput &&
34-
python3 /usr/src/signal_documentation/src/manage.py loaddata ./fixtures/available_geography.json &&
35-
python3 /usr/src/signal_documentation/src/manage.py loaddata ./fixtures/pathogens.json &&
36-
python3 /usr/src/signal_documentation/src/manage.py loaddata ./fixtures/signal_types.json &&
37-
python3 /usr/src/signal_documentation/src/manage.py loaddata ./fixtures/signal_categories.json &&
38-
python3 /usr/src/signal_documentation/src/manage.py loaddata ./fixtures/county.json &&
39-
python3 /usr/src/signal_documentation/src/manage.py loaddata ./fixtures/hhs.json &&
40-
python3 /usr/src/signal_documentation/src/manage.py loaddata ./fixtures/hrr.json &&
41-
python3 /usr/src/signal_documentation/src/manage.py loaddata ./fixtures/msa.json &&
42-
python3 /usr/src/signal_documentation/src/manage.py loaddata ./fixtures/state.json &&
34+
python3 /usr/src/signal_documentation/src/manage.py loaddata ./fixtures/* &&
4335
python3 /usr/src/signal_documentation/src/manage.py runserver 0.0.0.0:8000"
4436
volumes:
4537
- .:/usr/src/signal_documentation

nginx/default.conf.template

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
server {
22
listen 80;
33
server_name sdnginx;
4-
5-
location /static/ {
4+
location ~* /static/(.*)$ {
65
autoindex on;
7-
alias /staticfiles/;
6+
alias /staticfiles/$1;
87
}
98

109
location / {

src/base/admin.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
DescriptedFilter,
77
DescriptedFilterField,
88
Link,
9+
License
910
)
1011

1112

1213
class DescriptedFilterFieldInline(admin.TabularInline):
1314
model = DescriptedFilterField
14-
fields = ('description',)
15+
fields = ('description', 'filter_field')
1516
extra = 0
1617
can_create = False
1718

@@ -27,3 +28,12 @@ class LinkAdmin(admin.ModelAdmin):
2728
Admin interface for managing link objects.
2829
"""
2930
list_display: tuple[Literal['url'], Literal['link_type']] = ('url', 'link_type')
31+
32+
33+
@admin.register(License)
34+
class GeographyAdmin(admin.ModelAdmin):
35+
"""
36+
Admin interface for managing license objects.
37+
"""
38+
list_display: tuple[Literal['name'], Literal['use_restrictions']] = ('name', 'use_restrictions')
39+
search_fields: tuple[Literal['name']] = ('name',)

src/base/migrations/0004_license.py

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Generated by Django 4.2.10 on 2024-06-07 12:15
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('base', '0003_descriptedfilter_alter_link_link_type_and_more'),
10+
]
11+
12+
operations = [
13+
migrations.CreateModel(
14+
name='License',
15+
fields=[
16+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
17+
('name', models.CharField(help_text='License', max_length=256, unique=True)),
18+
('use_restrictions', models.TextField(blank=True, help_text='Use Restrictions', null=True)),
19+
],
20+
),
21+
]

src/base/models.py

+17
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,20 @@ def get_preview(self) -> LinkPreview:
123123
return {
124124
'description': _('No description available'),
125125
}
126+
127+
128+
class License(models.Model):
129+
"""
130+
A model representing a License.
131+
"""
132+
name: models.CharField = models.CharField(help_text=_('License'), max_length=256, unique=True)
133+
use_restrictions: models.TextField = models.TextField(help_text=_('Use Restrictions'), blank=True, null=True)
134+
135+
def __str__(self) -> str:
136+
"""
137+
Returns the name of the license as a string.
138+
139+
:return: The name of the license as a string.
140+
:rtype: str
141+
"""
142+
return self.name

src/datasources/admin.py

+1-4
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,11 @@ class SourceSubdivisionAdmin(ImportExportModelAdmin):
1212
"""
1313
Admin interface for managing source subdivision objects.
1414
"""
15-
list_display: tuple[Literal['name'], Literal['db_source']] = ('name', 'db_source')
15+
list_display: tuple[Literal['name'], Literal['db_source'], Literal['external_name']] = ('name', 'db_source', 'external_name')
1616
search_fields: tuple[Literal['name'], Literal['db_source']] = ('name', 'db_source')
1717
resource_classes: list[type[SourceSubdivisionResource]] = [SourceSubdivisionResource]
1818

1919

20-
data_source_search_fields_type = tuple[Literal['name'], Literal['source_subdivision__db_source'], Literal['source_subdivision__name'], Literal['description']]
21-
22-
2320
@admin.register(DataSource)
2421
class DataSourceAdmin(ImportExportModelAdmin):
2522
"""
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 5.0.3 on 2024-06-05 10:09
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('datasources', '0004_alter_datasource_options_and_more'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='sourcesubdivision',
15+
name='external_name',
16+
field=models.CharField(help_text='External Name', max_length=128, null=True),
17+
),
18+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Generated by Django 4.2.10 on 2024-06-07 12:15
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('base', '0004_license'),
11+
('datasources', '0005_sourcesubdivision_external_name'),
12+
]
13+
14+
operations = [
15+
migrations.AlterField(
16+
model_name='datasource',
17+
name='source_license',
18+
field=models.ForeignKey(help_text='License', on_delete=django.db.models.deletion.PROTECT, related_name='source_license', to='base.license'),
19+
),
20+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Generated by Django 4.2.10 on 2024-06-07 12:47
2+
3+
from django.db import migrations, models
4+
import django.db.models.deletion
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('base', '0004_license'),
11+
('datasources', '0006_alter_datasource_source_license'),
12+
]
13+
14+
operations = [
15+
migrations.AlterField(
16+
model_name='datasource',
17+
name='source_license',
18+
field=models.ForeignKey(help_text='License', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='data_sources', to='base.license'),
19+
),
20+
]

src/datasources/models.py

+12-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ class SourceSubdivision(TimeStampedModel):
1717
max_length=128,
1818
unique=True
1919
)
20+
external_name: models.CharField = models.CharField(
21+
help_text=_('External Name'),
22+
max_length=128,
23+
null=True,
24+
)
2025
description: models.TextField = models.TextField(
2126
help_text=_('Source description'),
2227
max_length=1000,
@@ -73,10 +78,15 @@ class DataSource(TimeStampedModel):
7378
null=True,
7479
blank=True
7580
)
76-
source_license: models.CharField = models.CharField(
81+
82+
source_license: models.ForeignKey = models.ForeignKey(
83+
'base.License',
84+
related_name='data_sources',
7785
help_text=_('License'),
78-
max_length=128
86+
on_delete=models.PROTECT,
87+
null=True
7988
)
89+
8090
links: models.ManyToManyField = models.ManyToManyField(
8191
'base.Link',
8292
help_text=_('DataSource links'),

src/datasources/resources.py

+14-3
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55
from import_export import resources
66
from import_export.fields import Field, widgets
77

8-
from base.models import Link, LinkTypeChoices
8+
from base.models import Link, LinkTypeChoices, License
99
from datasources.models import DataSource, SourceSubdivision
1010

1111

1212
class SourceSubdivisionResource(resources.ModelResource):
1313
name = Field(attribute='name', column_name='Source Subdivision')
1414
display_name = Field(attribute='display_name', column_name='Source Subdivision')
15+
external_name = Field(attribute='external_name', column_name='External Name')
1516
description = Field(attribute='description', column_name='Description')
1617
db_source = Field(attribute='db_source', column_name='DB Source')
1718
data_source = Field(
@@ -28,8 +29,8 @@ class SourceSubdivisionResource(resources.ModelResource):
2829
class Meta:
2930
model = SourceSubdivision
3031
fields: tuple[Literal['name'], Literal['display_name'], Literal['description'],
31-
Literal['data_source'], Literal['reference_signal'], Literal['links']]
32-
fields = ('name', 'display_name', 'description', 'data_source', 'reference_signal', 'links')
32+
Literal['data_source'], Literal['reference_signal'], Literal['links'], Literal['external_name']]
33+
fields = ('name', 'display_name', 'description', 'data_source', 'reference_signal', 'links', 'external_name')
3334
import_id_fields: list[str] = ['name']
3435
skip_unchanged = True
3536

@@ -39,6 +40,7 @@ def before_import_row(self, row, **kwargs) -> None:
3940
any additional links specified in 'DUA' or 'Link' columns.
4041
"""
4142
self.process_links(row)
43+
self.process_licenses(row)
4244
self.process_datasource(row)
4345

4446
def process_links(self, row) -> None:
@@ -57,6 +59,13 @@ def process_links(self, row) -> None:
5759
link, created = Link.objects.get_or_create(url=link_url, link_type=link_type)
5860
row['Links'] += row['Links'] + f'|{link.url}'
5961

62+
def process_licenses(self, row) -> None:
63+
if row['License']:
64+
license: License
65+
created: bool
66+
license, created = License.objects.get_or_create(name=row['License'])
67+
row['License'] = license
68+
6069
def process_datasource(self, row) -> None:
6170
if row['Name']:
6271
data_source: DataSource
@@ -70,4 +79,6 @@ def process_datasource(self, row) -> None:
7079
}
7180
)
7281
links: QuerySet[Link] = Link.objects.filter(url__in=row['Links'].split('|')).values_list('id', flat=True)
82+
license: License = License.objects.filter(name=row['License']).first()
7383
data_source.links.add(*links)
84+
data_source.source_license = license

src/fixtures/available_geography.json

+14
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
"pk": 1,
55
"fields": {
66
"name": "county",
7+
"display_name": "ADM2 (e.g. U.S. counties)",
8+
"order_id": 3,
79
"created": "2023-08-09T19:23:22.597131",
810
"modified": "2023-08-09T19:23:22.597131"
911
}
@@ -13,6 +15,8 @@
1315
"pk": 2,
1416
"fields": {
1517
"name": "hhs",
18+
"display_name": "HHS Regions",
19+
"order_id": 4,
1620
"created": "2023-08-09T19:23:22.597131",
1721
"modified": "2023-08-09T19:23:22.597131"
1822
}
@@ -22,6 +26,8 @@
2226
"pk": 3,
2327
"fields": {
2428
"name": "hrr",
29+
"display_name": "Hospital Referral Regions (HRRs)",
30+
"order_id": 6,
2531
"created": "2023-08-09T19:23:22.597131",
2632
"modified": "2023-08-09T19:23:22.597131"
2733
}
@@ -31,6 +37,8 @@
3137
"pk": 4,
3238
"fields": {
3339
"name": "msa",
40+
"display_name": "Metropolitan Statistical Areas (MSAs)",
41+
"order_id": 5,
3442
"created": "2023-08-09T19:23:22.597131",
3543
"modified": "2023-08-09T19:23:22.597131"
3644
}
@@ -40,6 +48,8 @@
4048
"pk": 5,
4149
"fields": {
4250
"name": "nation",
51+
"display_name": "National",
52+
"order_id": 1,
4353
"created": "2023-08-09T19:23:22.597131",
4454
"modified": "2023-08-09T19:23:22.597131"
4555
}
@@ -49,6 +59,8 @@
4959
"pk": 6,
5060
"fields": {
5161
"name": "state",
62+
"display_name": "ADM1 (e.g. U.S. states)",
63+
"order_id": 2,
5264
"created": "2023-08-09T19:23:22.597131",
5365
"modified": "2023-08-09T19:23:22.597131"
5466
}
@@ -58,6 +70,8 @@
5870
"pk": 7,
5971
"fields": {
6072
"name": "dma",
73+
"display_name": "Designated Market Areas (DMAs)",
74+
"order_id": 7,
6175
"created": "2023-08-09T19:23:22.597131",
6276
"modified": "2023-08-09T19:23:22.597131"
6377
}

0 commit comments

Comments
 (0)