Skip to content

Commit df4f7d0

Browse files
authored
Merge pull request #80 from cmu-delphi/OKRS24-72-Add-signals-sorting-functionality
Added possibility to manage filters hints from admin interface
2 parents 7141f6f + 9e76ec9 commit df4f7d0

File tree

7 files changed

+166
-33
lines changed

7 files changed

+166
-33
lines changed

src/base/admin.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,23 @@
22

33
from django.contrib import admin
44

5-
from base.models import Link
5+
from base.models import (
6+
DescriptedFilter,
7+
DescriptedFilterField,
8+
Link,
9+
)
10+
11+
12+
class DescriptedFilterFieldInline(admin.TabularInline):
13+
model = DescriptedFilterField
14+
fields = ('description',)
15+
extra = 0
16+
can_create = False
17+
18+
19+
@admin.register(DescriptedFilter)
20+
class DescriptedFilterAdmin(admin.ModelAdmin):
21+
inlines = [DescriptedFilterFieldInline]
622

723

824
@admin.register(Link)

src/base/context_processors.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from typing import Any
2+
3+
from base.models import DescriptedFilter
4+
from base.tools import split_class_name
5+
6+
7+
def filters_descriptions(request) -> dict[str, list[dict[str, Any]]]:
8+
"""
9+
Adds signal filters descriftions to the context.
10+
"""
11+
descripted_filters = DescriptedFilter.objects.all()
12+
13+
results: dict[str, dict[dict[str, dict[str, str]]]] = {
14+
'filters_descriptions': {
15+
split_class_name(str(df.filter_name))[-1]: df.descriptions
16+
} for df in descripted_filters
17+
}
18+
return results
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Generated by Django 5.0.2 on 2024-02-29 10:56
2+
3+
import django.db.models.deletion
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('base', '0002_link_created_link_modified'),
11+
]
12+
13+
operations = [
14+
migrations.CreateModel(
15+
name='DescriptedFilter',
16+
fields=[
17+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18+
('filter_name', models.CharField(choices=[('signals.filters.SignalFilter', 'signals.filters.SignalFilter')], max_length=256, unique=True)),
19+
],
20+
),
21+
migrations.AlterField(
22+
model_name='link',
23+
name='link_type',
24+
field=models.CharField(choices=[('api_documentation', 'API Documentation'), ('dua', 'DUA'), ('interpreting_mask', 'Interpreting mask use in context'), ('question_text', 'Question text'), ('survey_details', 'Survey details'), ('survey_documentation', 'Survey documentation'), ('technical_description', 'Technical description'), ('wave_10_revision', 'Wave 10 revision updates'), ('wave_11_revision', 'Wave 11 revision updates'), ('other', 'Other'), ('example_url', 'Example URL')], help_text='Link type', max_length=128),
25+
),
26+
migrations.CreateModel(
27+
name='DescriptedFilterField',
28+
fields=[
29+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
30+
('filter_field', models.CharField(help_text='Filter field', max_length=256)),
31+
('description', models.TextField(blank=True, help_text='Filter field description', null=True)),
32+
('filter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='filter_fields', to='base.descriptedfilter')),
33+
],
34+
options={
35+
'unique_together': {('filter', 'filter_field')},
36+
},
37+
),
38+
]

src/base/models.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,70 @@
33
from linkpreview import LinkPreview, link_preview
44
from models_extensions.models import TimeStampedModel
55

6+
from base.tools import get_class_by_name, split_class_name
7+
8+
FILTERS_LIST: list = [
9+
('signals.filters.SignalFilter', 'signals.filters.SignalFilter'),
10+
]
11+
12+
13+
class DescriptedFilterField(models.Model):
14+
"""
15+
A model representing a filter field that is descripted.
16+
"""
17+
filter = models.ForeignKey(
18+
'DescriptedFilter',
19+
on_delete=models.CASCADE,
20+
related_name='filter_fields'
21+
)
22+
filter_field = models.CharField(help_text=_('Filter field'), max_length=256)
23+
description = models.TextField(help_text=_('Filter field description'), blank=True, null=True)
24+
25+
class Meta:
26+
unique_together = ('filter', 'filter_field')
27+
28+
def __str__(self) -> str:
29+
"""
30+
Returns the name of the filter and the filter field
31+
that associated with description.
32+
"""
33+
return self.filter_field
34+
35+
36+
class DescriptedFilter(models.Model):
37+
"""
38+
A model representing a filter wich fields are descripted.
39+
"""
40+
filter_name = models.CharField(max_length=256, unique=True, choices=FILTERS_LIST)
41+
42+
def __str__(self) -> str:
43+
"""
44+
Returns the name of the filter and the filter field
45+
that associated with description.
46+
"""
47+
return self.filter_name
48+
49+
def save(self, *args, **kwargs) -> None:
50+
"""
51+
Saves the filter description.
52+
"""
53+
super().save(*args, **kwargs)
54+
if not self.filter_fields.exists():
55+
filter_class = get_class_by_name(*split_class_name(self.filter_name))
56+
for field_name, field in filter_class.base_filters.items():
57+
DescriptedFilterField.objects.create(
58+
filter=self,
59+
filter_field=field_name
60+
)
61+
super().save(*args, **kwargs)
62+
63+
@property
64+
def descriptions(self) -> dict:
65+
"""
66+
Returns a dictionary with filter fields descriptions.
67+
"""
68+
return {field.filter_field: field.description for field in self.filter_fields.all()}
69+
670

771
class LinkTypeChoices(models.TextChoices):
872
"""

src/base/tools.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import importlib
2+
from typing import Any
3+
4+
5+
def split_class_name(class_name) -> tuple[str, Any]:
6+
"""
7+
Splits a class name into module name and class name.
8+
"""
9+
parts = class_name.split('.')
10+
module_name = '.'.join(parts[:-1])
11+
class_name = parts[-1]
12+
return module_name, class_name
13+
14+
15+
def get_class_by_name(module_name, class_name) -> Any:
16+
"""
17+
Returns a class by its name.
18+
"""
19+
module = importlib.import_module(module_name)
20+
return getattr(module, class_name)

src/signal_documentation/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@
134134
'django.template.context_processors.request',
135135
'django.contrib.auth.context_processors.auth',
136136
'django.contrib.messages.context_processors.messages',
137+
'base.context_processors.filters_descriptions'
137138
],
138139
},
139140
},

src/templates/signals/signals.html

Lines changed: 8 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,7 @@ <h5 class="modal-title">Pathogen</h5>
3232
aria-label="Close"></button>
3333
</div>
3434
<div class="modal-body">
35-
Non omnis incidunt qui sed occaecati magni asperiores est mollitia. Soluta
36-
at et reprehenderit. Placeat autem numquam et fuga numquam. Tempora in
37-
facere consequatur sit dolor ipsum. Consequatur nemo amet incidunt est
38-
facilis. Dolorem neque recusandae quo sit molestias sint dignissimos.
35+
{{ filters_descriptions.SignalFilter.pathogen }}
3936
</div>
4037
<div class="modal-footer">
4138
<button type="button" class="btn btn-secondary"
@@ -63,10 +60,7 @@ <h5 class="modal-title">Active</h5>
6360
aria-label="Close"></button>
6461
</div>
6562
<div class="modal-body">
66-
Non omnis incidunt qui sed occaecati magni asperiores est mollitia. Soluta
67-
at et reprehenderit. Placeat autem numquam et fuga numquam. Tempora in
68-
facere consequatur sit dolor ipsum. Consequatur nemo amet incidunt est
69-
facilis. Dolorem neque recusandae quo sit molestias sint dignissimos.
63+
{{ filters_descriptions.SignalFilter.active }}
7064
</div>
7165
<div class="modal-footer">
7266
<button type="button" class="btn btn-secondary"
@@ -94,10 +88,7 @@ <h5 class="modal-title">Available Geography</h5>
9488
aria-label="Close"></button>
9589
</div>
9690
<div class="modal-body">
97-
Non omnis incidunt qui sed occaecati magni asperiores est mollitia. Soluta
98-
at et reprehenderit. Placeat autem numquam et fuga numquam. Tempora in
99-
facere consequatur sit dolor ipsum. Consequatur nemo amet incidunt est
100-
facilis. Dolorem neque recusandae quo sit molestias sint dignissimos.
91+
{{ filters_descriptions.SignalFilter.available_geography }}
10192
</div>
10293
<div class="modal-footer">
10394
<button type="button" class="btn btn-secondary"
@@ -125,10 +116,7 @@ <h5 class="modal-title">Signal Type</h5>
125116
aria-label="Close"></button>
126117
</div>
127118
<div class="modal-body">
128-
Non omnis incidunt qui sed occaecati magni asperiores est mollitia. Soluta
129-
at et reprehenderit. Placeat autem numquam et fuga numquam. Tempora in
130-
facere consequatur sit dolor ipsum. Consequatur nemo amet incidunt est
131-
facilis. Dolorem neque recusandae quo sit molestias sint dignissimos.
119+
{{ filters_descriptions.SignalFilter.signal_type }}
132120
</div>
133121
<div class="modal-footer">
134122
<button type="button" class="btn btn-secondary"
@@ -156,10 +144,7 @@ <h5 class="modal-title">Category</h5>
156144
aria-label="Close"></button>
157145
</div>
158146
<div class="modal-body">
159-
Non omnis incidunt qui sed occaecati magni asperiores est mollitia. Soluta
160-
at et reprehenderit. Placeat autem numquam et fuga numquam. Tempora in
161-
facere consequatur sit dolor ipsum. Consequatur nemo amet incidunt est
162-
facilis. Dolorem neque recusandae quo sit molestias sint dignissimos.
147+
{{ filters_descriptions.SignalFilter.category }}
163148
</div>
164149
<div class="modal-footer">
165150
<button type="button" class="btn btn-secondary"
@@ -187,10 +172,7 @@ <h5 class="modal-title">Format Type</h5>
187172
aria-label="Close"></button>
188173
</div>
189174
<div class="modal-body">
190-
Non omnis incidunt qui sed occaecati magni asperiores est mollitia. Soluta
191-
at et reprehenderit. Placeat autem numquam et fuga numquam. Tempora in
192-
facere consequatur sit dolor ipsum. Consequatur nemo amet incidunt est
193-
facilis. Dolorem neque recusandae quo sit molestias sint dignissimos.
175+
{{ filters_descriptions.SignalFilter.format_type }}
194176
</div>
195177
<div class="modal-footer">
196178
<button type="button" class="btn btn-secondary"
@@ -218,10 +200,7 @@ <h5 class="modal-title">Source</h5>
218200
aria-label="Close"></button>
219201
</div>
220202
<div class="modal-body">
221-
Non omnis incidunt qui sed occaecati magni asperiores est mollitia. Soluta
222-
at et reprehenderit. Placeat autem numquam et fuga numquam. Tempora in
223-
facere consequatur sit dolor ipsum. Consequatur nemo amet incidunt est
224-
facilis. Dolorem neque recusandae quo sit molestias sint dignissimos.
203+
{{ filters_descriptions.SignalFilter.source }}
225204
</div>
226205
<div class="modal-footer">
227206
<button type="button" class="btn btn-secondary"
@@ -249,10 +228,7 @@ <h5 class="modal-title">Time Label</h5>
249228
aria-label="Close"></button>
250229
</div>
251230
<div class="modal-body">
252-
Non omnis incidunt qui sed occaecati magni asperiores est mollitia. Soluta
253-
at et reprehenderit. Placeat autem numquam et fuga numquam. Tempora in
254-
facere consequatur sit dolor ipsum. Consequatur nemo amet incidunt est
255-
facilis. Dolorem neque recusandae quo sit molestias sint dignissimos.
231+
{{ filters_descriptions.SignalFilter.time_label }}
256232
</div>
257233
<div class="modal-footer">
258234
<button type="button" class="btn btn-secondary"

0 commit comments

Comments
 (0)