diff --git a/src/base/admin.py b/src/base/admin.py index 84e2dd2..e9e5d33 100644 --- a/src/base/admin.py +++ b/src/base/admin.py @@ -2,7 +2,23 @@ from django.contrib import admin -from base.models import Link +from base.models import ( + DescriptedFilter, + DescriptedFilterField, + Link, +) + + +class DescriptedFilterFieldInline(admin.TabularInline): + model = DescriptedFilterField + fields = ('description',) + extra = 0 + can_create = False + + +@admin.register(DescriptedFilter) +class DescriptedFilterAdmin(admin.ModelAdmin): + inlines = [DescriptedFilterFieldInline] @admin.register(Link) diff --git a/src/base/context_processors.py b/src/base/context_processors.py new file mode 100644 index 0000000..e3732f6 --- /dev/null +++ b/src/base/context_processors.py @@ -0,0 +1,18 @@ +from typing import Any + +from base.models import DescriptedFilter +from base.tools import split_class_name + + +def filters_descriptions(request) -> dict[str, list[dict[str, Any]]]: + """ + Adds signal filters descriftions to the context. + """ + descripted_filters = DescriptedFilter.objects.all() + + results: dict[str, dict[dict[str, dict[str, str]]]] = { + 'filters_descriptions': { + split_class_name(str(df.filter_name))[-1]: df.descriptions + } for df in descripted_filters + } + return results diff --git a/src/base/migrations/0003_descriptedfilter_alter_link_link_type_and_more.py b/src/base/migrations/0003_descriptedfilter_alter_link_link_type_and_more.py new file mode 100644 index 0000000..ce52438 --- /dev/null +++ b/src/base/migrations/0003_descriptedfilter_alter_link_link_type_and_more.py @@ -0,0 +1,38 @@ +# Generated by Django 5.0.2 on 2024-02-29 10:56 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('base', '0002_link_created_link_modified'), + ] + + operations = [ + migrations.CreateModel( + name='DescriptedFilter', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('filter_name', models.CharField(choices=[('signals.filters.SignalFilter', 'signals.filters.SignalFilter')], max_length=256, unique=True)), + ], + ), + migrations.AlterField( + model_name='link', + name='link_type', + 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), + ), + migrations.CreateModel( + name='DescriptedFilterField', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('filter_field', models.CharField(help_text='Filter field', max_length=256)), + ('description', models.TextField(blank=True, help_text='Filter field description', null=True)), + ('filter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='filter_fields', to='base.descriptedfilter')), + ], + options={ + 'unique_together': {('filter', 'filter_field')}, + }, + ), + ] diff --git a/src/base/models.py b/src/base/models.py index 8051529..d7998e3 100644 --- a/src/base/models.py +++ b/src/base/models.py @@ -3,6 +3,70 @@ from linkpreview import LinkPreview, link_preview from models_extensions.models import TimeStampedModel +from base.tools import get_class_by_name, split_class_name + +FILTERS_LIST: list = [ + ('signals.filters.SignalFilter', 'signals.filters.SignalFilter'), +] + + +class DescriptedFilterField(models.Model): + """ + A model representing a filter field that is descripted. + """ + filter = models.ForeignKey( + 'DescriptedFilter', + on_delete=models.CASCADE, + related_name='filter_fields' + ) + filter_field = models.CharField(help_text=_('Filter field'), max_length=256) + description = models.TextField(help_text=_('Filter field description'), blank=True, null=True) + + class Meta: + unique_together = ('filter', 'filter_field') + + def __str__(self) -> str: + """ + Returns the name of the filter and the filter field + that associated with description. + """ + return self.filter_field + + +class DescriptedFilter(models.Model): + """ + A model representing a filter wich fields are descripted. + """ + filter_name = models.CharField(max_length=256, unique=True, choices=FILTERS_LIST) + + def __str__(self) -> str: + """ + Returns the name of the filter and the filter field + that associated with description. + """ + return self.filter_name + + def save(self, *args, **kwargs) -> None: + """ + Saves the filter description. + """ + super().save(*args, **kwargs) + if not self.filter_fields.exists(): + filter_class = get_class_by_name(*split_class_name(self.filter_name)) + for field_name, field in filter_class.base_filters.items(): + DescriptedFilterField.objects.create( + filter=self, + filter_field=field_name + ) + super().save(*args, **kwargs) + + @property + def descriptions(self) -> dict: + """ + Returns a dictionary with filter fields descriptions. + """ + return {field.filter_field: field.description for field in self.filter_fields.all()} + class LinkTypeChoices(models.TextChoices): """ diff --git a/src/base/tools.py b/src/base/tools.py new file mode 100644 index 0000000..3f7e5be --- /dev/null +++ b/src/base/tools.py @@ -0,0 +1,20 @@ +import importlib +from typing import Any + + +def split_class_name(class_name) -> tuple[str, Any]: + """ + Splits a class name into module name and class name. + """ + parts = class_name.split('.') + module_name = '.'.join(parts[:-1]) + class_name = parts[-1] + return module_name, class_name + + +def get_class_by_name(module_name, class_name) -> Any: + """ + Returns a class by its name. + """ + module = importlib.import_module(module_name) + return getattr(module, class_name) diff --git a/src/signal_documentation/settings.py b/src/signal_documentation/settings.py index 35bbf33..ad66194 100644 --- a/src/signal_documentation/settings.py +++ b/src/signal_documentation/settings.py @@ -134,6 +134,7 @@ 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', + 'base.context_processors.filters_descriptions' ], }, }, diff --git a/src/templates/signals/signals.html b/src/templates/signals/signals.html index fca1ab5..39c3637 100644 --- a/src/templates/signals/signals.html +++ b/src/templates/signals/signals.html @@ -32,10 +32,7 @@ aria-label="Close">