Repository: ddionrails/ddionrails

Issue:
Add fields to Variable model (currently only in the search index)
### Fields

- [x] statistics -> JSONField
- [x] categories vs.
- ~uni ?~
- [x] scale

TODO:
- [x] fix broken test case in tests/functional/test_imports.py
- [x] make sure visualization has access to categories


Discussion:

Resolving patch:
diff --git a/ddionrails/data/imports.py b/ddionrails/data/imports.py
--- a/ddionrails/data/imports.py
+++ b/ddionrails/data/imports.py
@@ -40,12 +40,28 @@ def _import_variable(self, var, dataset, sort_id):
         variable.sort_id = sort_id
         variable.label = var.get("label", name)
         variable.label_de = var.get("label_de", name)
+        if "statistics" in var:
+            statistics = {
+                key: value
+                for key, value in zip(
+                    var["statistics"]["names"], var["statistics"]["values"]
+                )
+            }
+            variable.statistics = statistics
+        if "categories" in var:
+            values = var["categories"].get("values")
+            if values and len(values) > 0:
+                variable.categories = var["categories"]
+        variable.scale = var.get("scale", "")
         variable.save()
+        # remove statistics and scale before indexing into elasticsearch
+        var.pop("statistics", None)
+        var.pop("scale", None)
+        # Used for searching right now
+        # var.pop("categories", None)
         var["namespace"] = self.study.name
         var["dataset"] = dataset.name
         var["boost"] = dataset.boost
-        if "uni" not in var:
-            var["uni"] = var["categories"]
         variable.set_elastic(var)
 
 
diff --git a/ddionrails/data/models/variable.py b/ddionrails/data/models/variable.py
--- a/ddionrails/data/models/variable.py
+++ b/ddionrails/data/models/variable.py
@@ -3,10 +3,10 @@
 
 from __future__ import annotations
 
-import copy
-import json
 from collections import OrderedDict
+from typing import List, Dict
 
+from django.contrib.postgres.fields.jsonb import JSONField as JSONBField
 from django.db import models
 from django.shortcuts import get_object_or_404
 from django.urls import reverse
@@ -67,8 +67,17 @@ class Variable(ElasticMixin, DorMixin, models.Model):
     image_url = models.TextField(
         blank=True, verbose_name="Image URL", help_text="URL to a related image"
     )
-
-    image = FilerImageField(null=True, blank=True, on_delete=models.CASCADE)
+    statistics = JSONBField(
+        default=dict, null=True, blank=True, help_text="Statistics of the variable(JSON)"
+    )
+    scale = models.CharField(
+        max_length=255,
+        blank=True,
+        help_text="Scale of the variable",
+    )
+    categories = JSONBField(
+        default=list, null=True, blank=True, help_text="Categories of the variable(JSON)"
+    )
 
     # relations
     dataset = models.ForeignKey(
@@ -95,6 +104,7 @@ class Variable(ElasticMixin, DorMixin, models.Model):
         on_delete=models.CASCADE,
         help_text="Foreign key to concepts.Period",
     )
+    image = FilerImageField(null=True, blank=True, on_delete=models.CASCADE)
 
     # Used by ElasticMixin when indexed into Elasticsearch
     DOC_TYPE = "variable"
@@ -142,43 +152,26 @@ def html_description_long(self):
             html = ""
         return html
 
-    def get_categories(self):
-        if not hasattr(self, "category_list_cache"):
-            self.category_list_cache = self._construct_categories()
-        return self.category_list_cache
-
-    def get_name_cs(self):
-        """Get the case sensitive version of the variable name, if available"""
-        return self.get_source().get("name_cs", self.name)
-
-    def _construct_categories(self):
-        try:
-            c = self.get_source().get("uni", None)
-            if not c:
-                c = self.get_source().get("categories", dict())
-            x = []
-            for i in range(len(c["values"])):
-                x.append(
+    def get_categories(self) -> List:
+        if self.categories:
+            categories = []
+            for index in range(len(self.categories["values"])):
+                categories.append(
                     dict(
-                        value=c["values"][i],
-                        label=c["labels"][i],
-                        frequency=c["frequencies"][i],
-                        valid=(not c["missings"][i]),
+                        value=self.categories["values"][index],
+                        label=self.categories["labels"][index],
+                        label_de=self.categories["labels_de"][index],
+                        frequency=self.categories["frequencies"][index],
+                        valid=(not self.categories["missings"][index]),
                     )
                 )
-            return x
-        except:
+            return categories
+        else:
             return []
 
-    def get_statistics(self):
-        try:
-            c = self.get_source()["statistics"]
-            x = []
-            for i in range(len(c["values"])):
-                x.append(dict(name=c["names"][i], value=c["values"][i]))
-            return x
-        except:
-            return []
+    def get_name_cs(self):
+        """Get the case sensitive version of the variable name, if available"""
+        return self.get_source().get("name_cs", self.name)
 
     def get_study(self, default=None, id=False):
         try:
@@ -318,54 +311,34 @@ def get_origin_variables(self):
     def get_origin_questions(self):
         return self.get_origins_by_study_and_period(object_type="question")
 
-    def has_translations(self):
+    def has_translations(self) -> bool:
         return len(self.translation_languages()) > 0
 
-    def translation_languages(self):
+    def translation_languages(self) -> List[str]:
         if not hasattr(self, "languages"):
-            keys = list(self.get_source().keys())
-            self.languages = [x.replace("label_", "") for x in keys if ("label_" in x)]
+            self.languages = [
+                label.replace("label_", "")
+                for label in self.__dict__.keys()
+                if ("label_" in label)
+            ]
         return self.languages
 
-    def translate_item(self, language):
-        """DEPRECATED"""
-        var = copy.deepcopy(self.get_source())
-        var["label"] = var.get("label_%s" % language, var.get("label", ""))
-        try:
-            cat = var["categories"]
-            cat["label"] = cat.get("label_%s" % language, cat.get("label", ""))
-        except:
-            pass
-        return var
-
-    def translations(self):
-        """DEPRECATED"""
-        results = OrderedDict(en=self.get_source())
+    def translation_table(self) -> Dict:
+        translation_table = dict(label=dict(en=self.label))
         for language in self.translation_languages():
-            results[language] = self.translate_item(language=language)
-        return results
+            translation_table["label"][language] = getattr(self, f"label_{language}")
 
-    def translation_table(self):
-        s = self.get_source()
-        x = OrderedDict(label=OrderedDict(en=s.get("label", "")))
+        for category in self.get_categories():
+            translation_table[category["value"]] = dict(en=category["label"])
         for language in self.translation_languages():
-            x["label"][language] = s.get("label_" + language, "")
-        try:
-            for i in range(len(s["categories"]["values"])):
-                x[s["categories"]["values"][i]] = OrderedDict(
-                    en=s["categories"]["labels"][i]
+            for category in self.get_categories():
+                translation_table[category["value"]][language] = category.get(
+                    f"label_{language}"
                 )
-            for language in self.translation_languages():
-                for i in range(len(s["categories"]["values"])):
-                    x[s["categories"]["values"][i]][language] = s["categories"][
-                        "labels_" + language
-                    ][i]
-        except:
-            pass
-        return x
+        return translation_table
 
     def is_categorical(self) -> bool:
-        return len(self.get_categories()) > 0
+        return len(self.categories) > 0
 
     def title(self):
         return self.label if self.label != "" else self.name
@@ -376,13 +349,14 @@ def title_de(self):
         else:
             return self.title()
 
-    def to_dict(self):
+    def to_dict(self) -> Dict:
         return dict(
-            id=self.id,
             name=self.name,
             label=self.label,
             label_de=self.label_de,
             concept_id=self.concept_id,
+            scale=self.scale,
+            uni=self.categories,
         )
 
     def to_topic_dict(self, language="en"):
@@ -405,6 +379,3 @@ def index_prefetch(self, queryset):
             .prefetch_related("dataset__period")
             .prefetch_related("period")
         )
-
-    def to_json(self):
-        return json.dumps(self.to_dict())
diff --git a/ddionrails/data/views.py b/ddionrails/data/views.py
--- a/ddionrails/data/views.py
+++ b/ddionrails/data/views.py
@@ -125,6 +125,22 @@ def get_context_data(self, **kwargs):
             .filter(user_id=self.request.user.id)
             .all()
         )  # TODO user!
+
+        # Ordering of keys in statistics dictionary
+        context["statistics"] = dict()
+        ORDERING = (
+            "Min.",
+            "1st Qu.",
+            "Median",
+            "Mean",
+            "3rd Qu.",
+            "Max.",
+            "valid",
+            "invalid",
+        )
+        for measure in ORDERING:
+            if measure in self.object.statistics:
+                context["statistics"][measure] = self.object.statistics[measure]
         return context
 
 
@@ -137,8 +153,7 @@ def variable_json(
         dataset__name=dataset_name,
         name=variable_name,
     )
-    var_json = variable.get_source(as_json=False)
-    return JsonResponse(var_json)
+    return JsonResponse(variable.to_dict())
 
 
 def extend_context_for_variable(request, context):


Test patch:
diff --git a/tests/concepts/test_forms.py b/tests/concepts/test_forms.py
--- a/tests/concepts/test_forms.py
+++ b/tests/concepts/test_forms.py
@@ -8,7 +8,7 @@
     TopicForm,
 )
 
-pytestmark = [pytest.mark.concepts, pytest.mark.form]
+pytestmark = [pytest.mark.concepts, pytest.mark.forms] #pylint: disable=invalid-name
 
 
 class TestConceptForm:
diff --git a/tests/conftest.py b/tests/conftest.py
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -57,7 +57,24 @@ def dataset(db):
 def variable(db):
     """ A variable in the database """
     return VariableFactory(
-        name="some-variable", label="Some Variable", description="This is some variable"
+        name="some-variable",
+        label="Some Variable",
+        label_de="Eine Variable",
+        description="This is some variable",
+        statistics=dict(valid="1", invalid="0"),
+        categories={
+            "frequencies": [1, 0],
+            "labels": [
+                "[-6] Version of questionnaire with modified filtering",
+                "[1] Yes",
+            ],
+            "labels_de": [
+                "[-6] Fragebogenversion mit geaenderter Filterfuehrung",
+                "[1] Ja",
+            ],
+            "values": ["-6", "1"],
+            "missings": [True, False],
+        },
     )
 
 
diff --git a/tests/data/test_forms.py b/tests/data/test_forms.py
--- a/tests/data/test_forms.py
+++ b/tests/data/test_forms.py
@@ -2,7 +2,7 @@
 
 from ddionrails.data.forms import DatasetForm, VariableForm
 
-pytestmark = [pytest.mark.data, pytest.mark.form]
+pytestmark = [pytest.mark.data, pytest.mark.forms] #pylint: disable=invalid-name
 
 
 class TestDatasetForm:
diff --git a/tests/data/test_imports.py b/tests/data/test_imports.py
--- a/tests/data/test_imports.py
+++ b/tests/data/test_imports.py
@@ -1,3 +1,8 @@
+# -*- coding: utf-8 -*-
+# pylint: disable=missing-docstring,no-self-use
+
+""" Test cases for imports in ddionrails.data app """
+
 import pytest
 
 from ddionrails.concepts.models import AnalysisUnit, ConceptualDataset, Period
@@ -139,16 +144,91 @@ def test_import_dataset_method_with_dictionary(self, mocker, dataset_json_import
         mocked_import_variable.assert_called_once()
 
     def test_import_variable_method(self, mocker, dataset_json_importer, dataset):
+        assert 0 == Variable.objects.count()
+        mocked_set_elastic = mocker.patch.object(Variable, "set_elastic")
+        var = dict(
+            study="some-study",
+            dataset="some-dataset",
+            variable="some-variable",
+            statistics=dict(names=["valid", "invalid"], values=["1", "0"]),
+            scale="cat",
+            categories=dict(
+                frequencies=[1, 0],
+                labels=[
+                    "[-6] Version of questionnaire with modified filtering",
+                    "[1] Yes",
+                ],
+                labels_de=[
+                    "[-6] Fragebogenversion mit geaenderter Filterfuehrung",
+                    "[1] Ja",
+                ],
+                values=["-6", "1"],
+                missings=[True, False],
+            ),
+        )
+        dataset = dataset
+        sort_id = 0
+        dataset_json_importer._import_variable(var, dataset, sort_id)
+        assert 1 == Variable.objects.count()
+        variable = Variable.objects.first()
+        assert dataset == variable.dataset
+        assert "cat" == variable.scale
+        assert var["variable"] == variable.name
+        assert sort_id + 1 == variable.sort_id
+        assert "1" == variable.statistics["valid"]
+        assert "0" == variable.statistics["invalid"]
+        assert 1 == variable.categories["frequencies"][0]
+        assert (
+            "[-6] Version of questionnaire with modified filtering"
+            == variable.categories["labels"][0]
+        )
+        assert (
+            "[-6] Fragebogenversion mit geaenderter Filterfuehrung"
+            == variable.categories["labels_de"][0]
+        )
+        assert "-6" == variable.categories["values"][0]
+        assert True is variable.categories["missings"][0]
+        mocked_set_elastic.assert_called_once()
+
+    def test_import_variable_method_without_statistics(
+        self, mocker, dataset_json_importer, dataset
+    ):
+        assert 0 == Variable.objects.count()
+        mocked_set_elastic = mocker.patch.object(Variable, "set_elastic")
+        var = dict(
+            study="some-study",
+            dataset="some-dataset",
+            variable="some-variable",
+            statistics=dict(names=[], values=[]),
+        )
+        dataset = dataset
+        sort_id = 0
+        dataset_json_importer._import_variable(var, dataset, sort_id)
+        assert 1 == Variable.objects.count()
+        variable = Variable.objects.first()
+        assert dict() == variable.statistics
+        mocked_set_elastic.assert_called_once()
+
+    def test_import_variable_method_without_categories(
+        self, mocker, dataset_json_importer, dataset
+    ):
+        assert 0 == Variable.objects.count()
         mocked_set_elastic = mocker.patch.object(Variable, "set_elastic")
         var = dict(
             study="some-study",
             dataset="some-dataset",
             variable="some-variable",
-            categories=dict(frequencies=[], labels=[]),
+            statistics=dict(names=["valid", "invalid"], values=["1", "0"]),
+            categories=dict(
+                frequencies=[], labels=[], missings=[], values=[], labels_de=[]
+            ),
         )
         dataset = dataset
         sort_id = 0
         dataset_json_importer._import_variable(var, dataset, sort_id)
+        assert 1 == Variable.objects.count()
+        variable = Variable.objects.first()
+        assert [] == variable.categories
         mocked_set_elastic.assert_called_once()
 
     def test_import_variable_method_with_uni_key(
diff --git a/tests/data/test_models.py b/tests/data/test_models.py
--- a/tests/data/test_models.py
+++ b/tests/data/test_models.py
@@ -1,4 +1,5 @@
 # -*- coding: utf-8 -*-
+# pylint: disable=missing-docstring,no-self-use
 
 """ Test cases for models in ddionrails.data app """
 
@@ -20,6 +21,58 @@ def test_absolute_url_method(self, variable):
         )
         assert variable.get_absolute_url() == expected
 
+    def test_get_categories_method_without_categories(self, variable):
+        variable.categories = []
+        variable.save()
+        assert [] == variable.get_categories()
+
+    def test_get_categories_method(self, variable):
+        result = variable.get_categories()
+        expected = {
+            "value": "-6",
+            "label": "[-6] Version of questionnaire with modified filtering",
+            "label_de": "[-6] Fragebogenversion mit geaenderter Filterfuehrung",
+            "frequency": 1,
+            "valid": False,
+        }
+        assert expected == result[0]
+
+    def test_is_categorical_method(self, variable):
+        variable.categories = [dict(label="some-category")]
+        variable.save()
+        assert True is variable.is_categorical()
+
+    def test_is_categorical_method_fails(self, variable):
+        variable.categories = dict()
+        variable.save()
+        assert False is variable.is_categorical()
+
+    def test_has_translations(self, variable):
+        result = variable.has_translations()
+        assert True is result
+
+    def test_translation_languages(self, variable):
+        result = variable.translation_languages()
+        assert ["de"] == result
+
+    def test_translation_table(self, variable):
+        result = variable.translation_table()
+        expected = {
+            "label": {"en": variable.label, "de": variable.label_de},
+            "-6": {
+                "en": "[-6] Version of questionnaire with modified filtering",
+                "de": "[-6] Fragebogenversion mit geaenderter Filterfuehrung",
+            },
+            "1": {"en": "[1] Yes", "de": "[1] Ja"},
+        }
+        assert expected == result
+
+    def test_to_dict(self, variable):
+        result = variable.to_dict()
+        assert result["name"] == variable.name
+        assert result["scale"] == variable.scale
+        assert result["uni"] == variable.categories
+
 
 class TestDatasetModel:
     def test_string_method(self, dataset):
diff --git a/tests/data/test_views.py b/tests/data/test_views.py
--- a/tests/data/test_views.py
+++ b/tests/data/test_views.py
@@ -1,3 +1,8 @@
+# -*- coding: utf-8 -*-
+# pylint: disable=missing-docstring,no-self-use
+
+""" Test cases for views in ddionrails.data app """
+
 import json
 
 import pytest
@@ -6,7 +11,8 @@
 
 from ddionrails.data.models import Variable
 from ddionrails.data.views import DatasetRedirectView, RowHelper, VariableRedirectView
-from ddionrails.elastic.mixins import ModelMixin
+
+from tests import status
 
 pytestmark = [pytest.mark.data, pytest.mark.views]
 
@@ -31,23 +37,14 @@ def test_reset_method(self):
         assert row_helper.row_index == 0
 
 
-class TestExtendContextForVariable:
-    pass
-
-
 class TestDatasetDetailView:
-    def test_detail_view_with_existing_names(self, mocker, client, dataset):
-
-        # TODO: Template queries elasticsearch for study languages in navbar -> templates/nav/study.html
-        mocked_get_source = mocker.patch.object(ModelMixin, "get_source")
-        mocked_get_source.return_value = dict()
-
+    def test_detail_view_with_existing_names(self, client, dataset):
         url = reverse(
             "data:dataset",
             kwargs={"study_name": dataset.study.name, "dataset_name": dataset.name},
         )
         response = client.get(url)
-        assert response.status_code == 200
+        assert status.HTTP_200_OK == response.status_code
         template = "data/dataset_detail.html"
         assert template in (t.name for t in response.templates)
         assert response.context["dataset"] == dataset
@@ -62,10 +59,8 @@ def test_detail_view_with_invalid_study_name(self, client, dataset):
             "data:dataset",
             kwargs={"study_name": invalid_study_name, "dataset_name": dataset.name},
         )
-
-        # TODO view returns HttpResponseNotFound instead of raising Http404
         response = client.get(url)
-        assert response.status_code == 404
+        assert status.HTTP_404_NOT_FOUND == response.status_code
 
     def test_detail_view_with_invalid_dataset_name(self, client, dataset):
         invalid_dataset_name = "invalid-dataset-name"
@@ -86,7 +81,7 @@ class TestDatasetRedirectView:
     def test_redirect_view_with_valid_pk(self, rf, dataset):
         request = rf.get("dataset", kwargs={"pk": dataset.pk})
         response = DatasetRedirectView.as_view()(request, id=dataset.pk)
-        assert response.status_code == 302
+        assert status.HTTP_302_FOUND == response.status_code
 
     def test_redirect_view_with_invalid_pk(self, rf, dataset):
         invalid_dataset_id = 999
@@ -97,11 +92,6 @@ def test_redirect_view_with_invalid_pk(self, rf, dataset):
 
 class TestVariableDetailView:
     def test_detail_view_with_existing_names(self, client, mocker, variable):
-
-        # TODO: Template queries elasticsearch for study languages in navbar -> templates/nav/study.html
-        mocked_get_source = mocker.patch.object(ModelMixin, "get_source")
-        mocked_get_source.return_value = dict()
-
         url = reverse(
             "data:variable",
             kwargs={
@@ -112,15 +102,13 @@ def test_detail_view_with_existing_names(self, client, mocker, variable):
         )
         mocked_get_source = mocker.patch.object(Variable, "get_source")
         response = client.get(url)
-        assert response.status_code == 200
+        assert status.HTTP_200_OK == response.status_code
         template = "data/variable_detail.html"
         assert template in (t.name for t in response.templates)
 
-        # TODO : Test context. uses extend_context_for_variable()
-
 
 class TestVariableJsonView:
-    def test_json_view_with_existing_names(self, client, mocker, variable):
+    def test_json_view_with_existing_names(self, client, variable):
         url = reverse(
             "data:variable_json",
             kwargs={
@@ -129,21 +117,14 @@ def test_json_view_with_existing_names(self, client, mocker, variable):
                 "variable_name": variable.name,
             },
         )
-        mocked_get_source = mocker.patch.object(Variable, "get_source")
-        mocked_get_source.return_value = dict(
-            study=variable.dataset.study.name,
-            dataset=variable.dataset.name,
-            variable=variable.name,
-        )
         response = client.get(url)
-        assert response.status_code == 200
+        assert status.HTTP_200_OK == response.status_code
         assert response["Content-Type"] == "application/json"
+        content = response.json()
+        assert content['name'] == variable.name
+        assert content['scale'] == variable.scale
+        assert content['uni'] == variable.categories
 
-        # content = json.loads(response.content)
-        #
-        # assert content['study'] == variable.dataset.study.name
-        # assert content['dataset'] == variable.dataset.name
-        # assert content['variable'] == variable.name
 
     # TODO non existing study => 404
     def test_json_view_with_invalid_study_name(self, client, mocker, variable):
@@ -163,7 +144,7 @@ def test_preview_id_view_with_valid_pk(self, client, mocker, variable):
         url = reverse("api:variable_preview", kwargs={"variable_id": variable.pk})
         mocked_get_source = mocker.patch.object(Variable, "get_source")
         response = client.get(url)
-        assert response.status_code == 200
+        assert status.HTTP_200_OK == response.status_code
         template = "data/variable_preview.html"
         assert template in (t.name for t in response.templates)
         assert response["Content-Type"] == "text/plain"
@@ -182,14 +163,14 @@ def test_preview_id_view_with_invalid_pk(self, client, db):
 
         # TODO view returns HttpResponseNotFound instead of raising Http404
         response = client.get(url)
-        assert response.status_code == 404
+        assert status.HTTP_404_NOT_FOUND == response.status_code
 
 
 class TestVariableRedirectView:
     def test_redirect_view_with_valid_pk(self, rf, variable):
         request = rf.get("variable", kwargs={"pk": variable.pk})
         response = VariableRedirectView.as_view()(request, id=variable.pk)
-        assert response.status_code == 302
+        assert status.HTTP_302_FOUND == response.status_code
 
     def test_redirect_view_with_invalid_pk(self, db, rf):
         invalid_pk = 999
diff --git a/tests/functional/browser/test_javascript.py b/tests/functional/browser/test_javascript.py
--- a/tests/functional/browser/test_javascript.py
+++ b/tests/functional/browser/test_javascript.py
@@ -4,7 +4,7 @@
 
 import pytest
 
-pytestmark = [pytest.mark.functional, pytest.mark.django_db]
+pytestmark = [pytest.mark.functional, pytest.mark.django_db] #pylint: disable=invalid-name
 
 
 def test_studies_dropdown_menu(browser, live_server, study):
diff --git a/tests/functional/browser/test_navigation.py b/tests/functional/browser/test_navigation.py
--- a/tests/functional/browser/test_navigation.py
+++ b/tests/functional/browser/test_navigation.py
@@ -5,7 +5,7 @@
 
 import pytest
 
-pytestmark = [pytest.mark.functional, pytest.mark.django_db]
+pytestmark = [pytest.mark.functional, pytest.mark.django_db] #pylint: disable=invalid-name
 
 
 def test_get_contact_page_from_home(browser, live_server):
diff --git a/tests/functional/browser/test_workspace.py b/tests/functional/browser/test_workspace.py
--- a/tests/functional/browser/test_workspace.py
+++ b/tests/functional/browser/test_workspace.py
@@ -8,7 +8,7 @@
 from django.contrib.auth.models import User
 from splinter.exceptions import ElementDoesNotExist
 
-pytestmark = [pytest.mark.functional, pytest.mark.django_db]
+pytestmark = [pytest.mark.functional, pytest.mark.django_db] #pylint: disable=invalid-name
 
 
 def test_login_with_known_user(browser, login_url, known_user):
diff --git a/tests/functional/imports/test_system_import.py b/tests/functional/imports/test_system_import.py
--- a/tests/functional/imports/test_system_import.py
+++ b/tests/functional/imports/test_system_import.py
@@ -7,7 +7,7 @@
 from ddionrails.imports.management.commands import system
 from ddionrails.studies.models import Study
 
-pytestmark = [pytest.mark.imports, pytest.mark.functional]
+pytestmark = [pytest.mark.imports, pytest.mark.functional] #pylint: disable=invalid-name
 
 
 class TestSystemImport:
diff --git a/tests/functional/search/test_search.py b/tests/functional/search/test_search.py
--- a/tests/functional/search/test_search.py
+++ b/tests/functional/search/test_search.py
@@ -8,7 +8,7 @@
 from ddionrails.imports.manager import StudyImportManager
 from ddionrails.publications.models import Publication
 
-pytestmark = [pytest.mark.functional]
+pytestmark = [pytest.mark.functional] #pylint: disable=invalid-name
 
 
 @pytest.fixture()
diff --git a/tests/functional/test_data/some-study/ddionrails/datasets/some-dataset.json b/tests/functional/test_data/some-study/ddionrails/datasets/some-dataset.json
--- a/tests/functional/test_data/some-study/ddionrails/datasets/some-dataset.json
+++ b/tests/functional/test_data/some-study/ddionrails/datasets/some-dataset.json
@@ -4,13 +4,27 @@
     "variable": "some-variable",
     "study": "some-study",
     "categories": {
+      "frequencies": [
+        1,
+        0
+      ],
+      "labels": [
+        "[-6] Version of questionnaire with modified filtering",
+        "[1] Yes"
+      ],
+      "labels_de": [
+        "[-6] Fragebogenversion mit geaenderter Filterfuehrung",
+        "[1] Ja"
+      ],
+      "missings": [
+        true,
+        false
+      ]
     }
   },
   "some-other-variable": {
     "name": "some-other-variable",
     "variable": "some-other-variable",
-    "study": "some-study",
-    "categories": {
-    }
+    "study": "some-study"
   }
 }
\ No newline at end of file
diff --git a/tests/instruments/test_models.py b/tests/instruments/test_models.py
--- a/tests/instruments/test_models.py
+++ b/tests/instruments/test_models.py
@@ -5,7 +5,7 @@
 
 from ddionrails.instruments.models import ConceptQuestion
 
-pytestmark = [pytest.mark.instrument, pytest.mark.models]
+pytestmark = [pytest.mark.instruments, pytest.mark.models]
 
 
 class TestInstrumentModel:
diff --git a/tests/studies/test_forms.py b/tests/studies/test_forms.py
--- a/tests/studies/test_forms.py
+++ b/tests/studies/test_forms.py
@@ -2,7 +2,7 @@
 
 from ddionrails.studies.forms import StudyForm, StudyInitialForm
 
-pytestmark = [pytest.mark.studies, pytest.mark.form]
+pytestmark = [pytest.mark.studies, pytest.mark.forms] #pylint: disable=invalid-name
 
 
 class TestStudyForm: