Skip to content

Commit eb60540

Browse files
authored
Merge pull request #13 from cmu-delphi/SD-26_v3
SD-26
2 parents 77a7368 + cce97a4 commit eb60540

File tree

9 files changed

+93
-100
lines changed

9 files changed

+93
-100
lines changed

src/datasources/admin.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from import_export.admin import ImportExportModelAdmin
33

44
from datasources.models import DataSource, SourceSubdivision
5-
from datasources.resources import DataSourceResource, SourceSubdivisionResource
5+
from datasources.resources import SourceSubdivisionResource
66

77

88
@admin.register(SourceSubdivision)
@@ -22,4 +22,3 @@ class DataSourceAdmin(ImportExportModelAdmin):
2222
"""
2323
list_display = ('name',)
2424
search_fields = ('name', 'source_subdivision__db_source', 'source_subdivision__name', 'description')
25-
resource_classes = [DataSourceResource]

src/datasources/resources.py

Lines changed: 18 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -7,67 +7,6 @@
77
from datasources.models import DataSource, SourceSubdivision
88

99

10-
class DataSourceResource(resources.ModelResource):
11-
"""
12-
A resource for importing and exporting data sources using the Django ORM.
13-
"""
14-
name = Field(attribute='name', column_name='Name')
15-
display_name = Field(attribute='display_name', column_name='Name')
16-
description = Field(attribute='description', column_name='Description')
17-
source_license = Field(attribute='source_license', column_name='License')
18-
links = Field(
19-
attribute='links',
20-
column_name='Links',
21-
widget=widgets.ManyToManyWidget(Link, field='url', separator='|'),
22-
)
23-
source_subdivisions = Field(
24-
attribute='source_subdivisions',
25-
column_name='Name',
26-
widget=widgets.ForeignKeyWidget(DataSource, field='name'),
27-
)
28-
29-
class Meta:
30-
model = DataSource
31-
fields = ('name', 'display_name', 'description', 'source_license', 'links')
32-
import_id_fields = ['name']
33-
34-
def before_import_row(self, row, **kwargs):
35-
"""
36-
Hook called before importing each row. Modifies 'Links' column to include
37-
any additional links specified in 'DUA' or 'Link' columns.
38-
"""
39-
self.process_links(row)
40-
self.process_subdivisions(row)
41-
42-
def process_subdivisions(self, row):
43-
if row['Source Subdivision']:
44-
data_source = DataSource.objects.get(name=row['Name'])
45-
source_subdivision, created = SourceSubdivision.objects.get_or_create(
46-
name=row['Source Subdivision'],
47-
defaults={
48-
'display_name': row['Source Subdivision'],
49-
'description': row['Description'],
50-
'db_source': row['DB Source'],
51-
'data_source': data_source
52-
}
53-
)
54-
55-
def process_links(self, row):
56-
row['Links'] = ''
57-
if row['DUA']:
58-
link, created = Link.objects.get_or_create(url=row['DUA'], defaults={'link_type': LinkTypeChoices.DUA})
59-
row['Links'] += row['Links'] + f'|{link.url}'
60-
61-
if row['Link']:
62-
pattern = r'\[(.*?)\]\((.*?)\)'
63-
pattern_match = re.search(pattern, row['Link'])
64-
link_type_mapping = {choice.label: choice.value for choice in LinkTypeChoices}
65-
link_type = link_type_mapping[pattern_match.group(1)]
66-
link_url = pattern_match.group(2)
67-
link, created = Link.objects.get_or_create(url=link_url, link_type=link_type)
68-
row['Links'] += row['Links'] + f'|{link.url}'
69-
70-
7110
class SourceSubdivisionResource(resources.ModelResource):
7211
name = Field(attribute='name', column_name='Source Subdivision')
7312
display_name = Field(attribute='display_name', column_name='Source Subdivision')
@@ -86,7 +25,7 @@ class SourceSubdivisionResource(resources.ModelResource):
8625

8726
class Meta:
8827
model = SourceSubdivision
89-
fields = ('name', 'display_name', 'description', 'links', 'data_source')
28+
fields = ('name', 'display_name', 'description', 'data_source', 'reference_signal', 'links')
9029
import_id_fields = ['name']
9130
skip_unchanged = True
9231

@@ -95,6 +34,10 @@ def before_import_row(self, row, **kwargs) -> None:
9534
Hook called before importing each row. Modifies 'Links' column to include
9635
any additional links specified in 'DUA' or 'Link' columns.
9736
"""
37+
self.process_links(row)
38+
self.process_datasource(row)
39+
40+
def process_links(self, row):
9841
row['Links'] = ''
9942
if row['DUA']:
10043
link, created = Link.objects.get_or_create(url=row['DUA'], link_type=LinkTypeChoices.DUA)
@@ -107,3 +50,16 @@ def before_import_row(self, row, **kwargs) -> None:
10750
link_url = pattern_match.group(2)
10851
link, created = Link.objects.get_or_create(url=link_url, link_type=link_type)
10952
row['Links'] += row['Links'] + f'|{link.url}'
53+
54+
def process_datasource(self, row):
55+
if row['Name']:
56+
data_source, created = DataSource.objects.get_or_create(
57+
name=row['Name'],
58+
defaults={
59+
'display_name': row['Name'],
60+
'description': row['Description'],
61+
'source_license': row['License'],
62+
}
63+
)
64+
links = Link.objects.filter(url__in=row['Links'].split('|')).values_list('id', flat=True)
65+
data_source.links.add(*links)

src/signals/admin.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,19 @@
55
Geography,
66
Pathogen,
77
Signal,
8+
SignalCategory,
89
SignalType,
910
)
10-
from signals.resources import SignalResource
11+
from signals.resources import SignalBaseResource, SignalResource
12+
13+
14+
@admin.register(SignalCategory)
15+
class SignalCategoryAdmin(admin.ModelAdmin):
16+
"""
17+
Admin interface for managing signal category objects.
18+
"""
19+
list_display = ('name',)
20+
search_fields = ('name',)
1121

1222

1323
@admin.register(Geography)
@@ -55,4 +65,4 @@ class SignalAdmin(ImportExportModelAdmin):
5565
'has_stderr',
5666
'has_sample_size',
5767
)
58-
resource_classes = [SignalResource]
68+
resource_classes = [SignalResource, SignalBaseResource]

src/signals/migrations/0001_initial.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Generated by Django 4.2.2 on 2023-06-13 11:51
1+
# Generated by Django 4.2.3 on 2023-07-24 16:10
22

33
from django.db import migrations, models
44
import django.db.models.deletion
@@ -9,8 +9,8 @@ class Migration(migrations.Migration):
99
initial = True
1010

1111
dependencies = [
12+
('datasources', '0002_alter_sourcesubdivision_db_source'),
1213
('base', '0001_initial'),
13-
('datasources', '0001_initial'),
1414
]
1515

1616
operations = [
@@ -38,6 +38,9 @@ class Migration(migrations.Migration):
3838
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
3939
('name', models.CharField(help_text='Name', max_length=128, unique=True)),
4040
],
41+
options={
42+
'verbose_name_plural': 'signal categories',
43+
},
4144
),
4245
migrations.CreateModel(
4346
name='SignalType',
@@ -53,7 +56,8 @@ class Migration(migrations.Migration):
5356
name='Signal',
5457
fields=[
5558
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
56-
('name', models.CharField(help_text='Name', max_length=128, unique=True)),
59+
('name', models.CharField(help_text='Name', max_length=128)),
60+
('display_name', models.CharField(help_text='Display Name', max_length=128)),
5761
('active', models.BooleanField(default=False, help_text='Active')),
5862
('short_description', models.TextField(blank=True, help_text='Short Description', max_length=500, null=True)),
5963
('description', models.TextField(blank=True, help_text='Description', max_length=1000, null=True)),
@@ -67,12 +71,15 @@ class Migration(migrations.Migration):
6771
('has_sample_size', models.BooleanField(default=False, help_text='Has Sample Size')),
6872
('high_values_are', models.CharField(choices=[('bad', 'Bad'), ('good', 'Good'), ('neutral', 'Neutral')], help_text='High values are', max_length=128)),
6973
('available_geography', models.ManyToManyField(help_text='Available geography', to='signals.geography')),
70-
('base', models.ForeignKey(blank=True, help_text='Signal base', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='base_for', to='signals.signal')),
74+
('base', models.ForeignKey(blank=True, help_text='Signal base', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='base_for', to='signals.signal')),
7175
('category', models.ForeignKey(help_text='Signal Category', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='signals', to='signals.signalcategory')),
7276
('links', models.ManyToManyField(help_text='Signal links', related_name='signals', to='base.link')),
73-
('pathogen', models.ForeignKey(help_text='Pathogen/Disease Area', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='signals', to='signals.pathogen')),
77+
('pathogen', models.ManyToManyField(help_text='Pathogen/Disease Area', related_name='signals', to='signals.pathogen')),
7478
('signal_type', models.ManyToManyField(help_text='Signal Type', related_name='signals', to='signals.signaltype')),
7579
('source', models.ForeignKey(help_text='Source Subdivision', on_delete=django.db.models.deletion.PROTECT, related_name='signals', to='datasources.sourcesubdivision')),
7680
],
81+
options={
82+
'unique_together': {('name', 'source')},
83+
},
7784
),
7885
]

src/signals/migrations/0002_remove_signal_pathogen_signal_pathogen.py

Lines changed: 0 additions & 22 deletions
This file was deleted.

src/signals/models.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ class SignalCategory(models.Model):
5050
unique=True
5151
)
5252

53+
class Meta:
54+
verbose_name_plural = "signal categories"
55+
5356
def __str__(self) -> str:
5457
"""
5558
Returns the name of the signal category as a string.
@@ -134,13 +137,16 @@ class Signal(models.Model):
134137
name = models.CharField(
135138
help_text=_('Name'),
136139
max_length=128,
137-
unique=True
140+
)
141+
display_name = models.CharField(
142+
help_text=_('Display Name'),
143+
max_length=128,
138144
)
139145
base = models.ForeignKey(
140146
'signals.Signal',
141147
related_name='base_for',
142148
help_text=_('Signal base'),
143-
on_delete=models.PROTECT,
149+
on_delete=models.SET_NULL,
144150
null=True,
145151
blank=True
146152
)
@@ -233,6 +239,9 @@ class Signal(models.Model):
233239
on_delete=models.PROTECT,
234240
)
235241

242+
class Meta:
243+
unique_together = ['name', 'source']
244+
236245
def __str__(self) -> str:
237246
"""
238247
Returns the name of the signal as a string.

src/signals/resources.py

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,43 @@
1515
)
1616

1717

18+
class SignalBaseResource(resources.ModelResource):
19+
name = Field(attribute='name', column_name='Signal')
20+
display_name = Field(attribute='display_name', column_name='Name')
21+
base = Field(
22+
attribute='base',
23+
column_name='base',
24+
widget=widgets.ForeignKeyWidget(Signal, field='id'),
25+
)
26+
source = Field(
27+
attribute='source',
28+
column_name='Source Subdivision',
29+
widget=widgets.ForeignKeyWidget(SourceSubdivision, field='name'),
30+
)
31+
32+
class Meta:
33+
model = Signal
34+
fields = ['base']
35+
import_id_fields = ['name', 'source', 'display_name']
36+
37+
def before_import_row(self, row, **kwargs):
38+
"""Post-processes each row after importing."""
39+
self.process_base(row)
40+
41+
def process_base(self, row):
42+
"""Processes base."""
43+
44+
if row['Signal BaseName']:
45+
source = SourceSubdivision.objects.get(name=row['Source Subdivision'])
46+
base_signal = Signal.objects.get(name=row['Signal BaseName'], source=source)
47+
row['base'] = base_signal.id
48+
49+
1850
class SignalResource(resources.ModelResource):
1951
"""Resource class for importing and exporting Signal models."""
2052

21-
name = Field(attribute='name', column_name='Name')
53+
name = Field(attribute='name', column_name='Signal')
54+
display_name = Field(attribute='display_name', column_name='Name')
2255
pathogen = Field(
2356
attribute='pathogen',
2457
column_name='Pathogen/ Disease Area',
@@ -66,6 +99,7 @@ class Meta:
6699
model = Signal
67100
fields = [
68101
'name',
102+
'display_name',
69103
'pathogen',
70104
'signal_type',
71105
'active',
@@ -84,7 +118,7 @@ class Meta:
84118
'source',
85119
'links'
86120
]
87-
import_id_fields = ['name']
121+
import_id_fields = ['name', 'source', 'display_name']
88122

89123
def before_import_row(self, row, **kwargs):
90124
"""Pre-processes each row before importing."""

src/templates/signals/signal_detail.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ <h1>Signal</h1>
1111
<ol class="breadcrumb">
1212
<li class="breadcrumb-item"><a href="{% url 'signals' %}">Home</a></li>
1313
<li class="breadcrumb-item"><a href="{% url 'signals' %}">Signals</a></li>
14-
<li class="breadcrumb-item active">{{ signal.name }}</li>
14+
<li class="breadcrumb-item active">{{ signal.display_name }}</li>
1515
</ol>
1616
</nav>
1717
</div><!-- End Page Title -->
@@ -24,7 +24,7 @@ <h1>Signal</h1>
2424
<div class="card-body pt-3">
2525

2626
<div class="tab-pane fade show active profile-overview" id="profile-overview" role="tabpanel">
27-
<h5 class="card-title">{{signal.name }}</h5>
27+
<h5 class="card-title">{{signal.display_name }}</h5>
2828
<p class="small fst-italic">{{ signal.description }}</p>
2929

3030
<h5 class="card-title">Details</h5>

src/templates/signals/signal_list.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ <h5 class="card-title">Signals</h5>
4545
<tbody>
4646
{% for signal in signals %}
4747
<tr class="clickable-table-row" onClick="location.href='{% url 'signal' pk=signal.id %}';">
48-
<td>{{ signal.name }}</td>
48+
<td>{{ signal.display_name }}</td>
4949
<td>{{ signal.source }}</td>
5050
<td>{{ signal.description }}</td>
5151
<td>

0 commit comments

Comments
 (0)