We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Move import_path() method into mixin
### Subject of the issue

The System and Study model both implement the same `import_path()` method.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `ddionrails/base/mixins.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 
3 """ Mixins for ddionrails.base app """
4 
5 from typing import Dict
6 
7 from django import forms
8 
9 from config.helpers import render_markdown
10 
11 
12 class ModelMixin:
13     """
14     Default mixins for all classes in DDI on Rails.
15 
16     Requires two definition in the ``DOR`` class:
17 
18     * io_fields: Fields that are used for the default form and in the default dict.
19     * id_fields: Fields that are used for the get_or_create default method.
20 
21     Example:
22 
23     ::
24 
25         from django.db import models
26         from ddionrails.mixins import ModelMixin
27 
28         class Test(models.Model, ModelMixin):
29 
30             name = models.CharField(max_length=255, unique=True)
31 
32             class DOR:
33                 id_fields = ["name"]
34                 io_fields = ["name"]
35 
36     The default value for DOR is:
37 
38     ::
39 
40         class DOR:
41             id_fields = ["name"]
42             io_fields = ["name", "label", "description"]
43 
44     The ``id_fields`` are also use to construct a default string identifier.
45     It is therefore recommended, to order them from the most general to the
46     most specific one.
47 
48     """
49 
50     class DOR:
51         id_fields = ["name"]
52         io_fields = ["name", "label", "description"]
53 
54     @classmethod
55     def get_or_create(cls, parameters: Dict, lower_strings: bool = True):
56         """
57         Default for the get_or_create based on a dict.
58 
59         The method uses only relevant identifiers based on ``DOR.id_fields``.
60 
61         By default, all strings are set to lower case (option ``lower_strings``).
62         """
63         definition = {key: parameters[key] for key in cls.DOR.id_fields}
64         for key, value in definition.items():
65             if value.__class__ == str and lower_strings:
66                 definition[key] = value.lower()
67         return cls.objects.get_or_create(**definition)[0]
68 
69     @classmethod
70     def get(cls, parameters: Dict):
71         """
72         Default for the get_or_create based on a dict.
73 
74         The method uses only relevant identifiers based on ``DOR.id_fields``.
75         """
76         try:
77             definition = {key: parameters[key] for key in cls.DOR.id_fields}
78             result = cls.objects.get(**definition)
79         except cls.DoesNotExist:
80             result = None
81         return result
82 
83     @classmethod
84     def default_form(cls):
85         """
86         Creates a default form for all attributes defined in ``DOR.io_fields``.
87         """
88 
89         class DefaultForm(forms.ModelForm):
90             class Meta:
91                 model = cls
92                 fields = cls.DOR.io_fields
93 
94         return DefaultForm
95 
96     def to_dict(self) -> Dict:
97         """
98         Uses the ``DOR.io_fields`` attribute to generate a default
99         dict object for the current instance.
100         """
101         dictionary = dict()
102         for field in self.DOR.io_fields:
103             value = getattr(self, field)
104             try:
105                 dictionary[field] = value.pk
106             except AttributeError:
107                 dictionary[field] = value
108         return dictionary
109 
110     def title(self):
111         """
112         Default for the title. It first looks for a valid label, next for a
113         valid name, and otherwise returns an empty string.
114         """
115         try:
116             name = self.name
117         except AttributeError:
118             name = ""
119         try:
120             label = self.label
121         except AttributeError:
122             label = ""
123         return name if label == "" else label
124 
125     def html_description(self):
126         """
127         Uses the ddionrails Markdown parser (ddionrails.helpers) to render
128         the description into HTML.
129         """
130         try:
131             html = render_markdown(self.description)
132         except AttributeError:
133             html = ""
134         return html
135 
136     def __str__(self):
137         """ Returns a string reprensentation of the instance, using DOR.id_fields """
138         result = []
139         for field in self.DOR.id_fields:
140             value = getattr(self, field)
141             try:
142                 result.append(value.string_id())
143             except AttributeError:
144                 result.append(str(value))
145         return "/".join(result)
146 
147 
148 class AdminMixin:
149     """ A mixin for ModelAdmins to query related models via methods """
150 
151     @staticmethod
152     def study_name(obj):
153         """ Return the name of the related study """
154         try:
155             return obj.study.name
156         except AttributeError:
157             return None
158 
159     @staticmethod
160     def period_name(obj):
161         """ Return the name of the related period """
162         try:
163             return obj.period.name
164         except AttributeError:
165             return None
166 
167     @staticmethod
168     def analysis_unit_name(obj):
169         """ Return the name of the related analysis_unit """
170         try:
171             return obj.analysis_unit.name
172         except AttributeError:
173             return None
174 
175     @staticmethod
176     def dataset_name(obj):
177         """ Return the name of the related dataset """
178         try:
179             return obj.dataset.name
180         except AttributeError:
181             return None
182 
183     @staticmethod
184     def dataset_study_name(obj):
185         """ Return the name of the related dataset.study """
186         try:
187             return obj.dataset.study.name
188         except AttributeError:
189             return None
190 
191     @staticmethod
192     def instrument_name(obj):
193         """ Return the name of the related instrument """
194         try:
195             return obj.instrument.name
196         except AttributeError:
197             return None
198 
199     @staticmethod
200     def instrument_study_name(obj):
201         """ Return the name of the related instrument.study """
202         try:
203             return obj.instrument.study.name
204         except AttributeError:
205             return None
206 
207     @staticmethod
208     def basket_name(obj):
209         """ Return the name of the related basket """
210         try:
211             return obj.basket.name
212         except AttributeError:
213             return None
214 
215     @staticmethod
216     def basket_study_name(obj):
217         """ Return the name of the related basket.study """
218         try:
219             return obj.basket.study.name
220         except AttributeError:
221             return None
222 
223     @staticmethod
224     def user_name(obj):
225         """ Return the name of the related basket.user """
226         try:
227             return obj.basket.user.username
228         except AttributeError:
229             return None
230
```

Path: `ddionrails/studies/models.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 """ Model definitions for ddionrails.studies app """
3 
4 import os
5 from typing import List, Optional
6 
7 from django.conf import settings
8 from django.contrib.postgres.fields import ArrayField, JSONField
9 from django.contrib.postgres.fields.jsonb import JSONField as JSONBField
10 from django.db import models
11 from django.urls import reverse
12 from model_utils.models import TimeStampedModel
13 
14 from ddionrails.base.mixins import ModelMixin
15 
16 
17 class TopicList(models.Model):
18 
19     # attributes
20     topiclist = JSONBField(
21         default=list,
22         null=True,
23         blank=True,
24         help_text="Topics of the related study (JSON)",
25     )
26 
27     # relations
28     study = models.OneToOneField(
29         "Study",
30         blank=True,
31         null=True,
32         related_name="topiclist",
33         on_delete=models.CASCADE,
34         help_text="OneToOneField to studies.Study",
35     )
36 
37 
38 class Study(ModelMixin, TimeStampedModel):
39     """
40     Stores a single study,
41     related to :model:`data.Dataset`, :model:`instruments.Instrument`,
42     :model:`concepts.Period` and :model:`workspace.Basket`.
43     """
44 
45     # attributes
46     name = models.CharField(
47         max_length=255, unique=True, db_index=True, help_text="Name of the study"
48     )
49     label = models.CharField(
50         max_length=255,
51         blank=True,
52         verbose_name="Label (English)",
53         help_text="Label of the study (English)",
54     )
55     label_de = models.CharField(
56         max_length=255,
57         blank=True,
58         null=True,
59         verbose_name="Label (German)",
60         help_text="Label of the study (German)",
61     )
62     description = models.TextField(
63         blank=True, help_text="Description of the study (Markdown)"
64     )
65     repo = models.CharField(
66         max_length=255,
67         blank=True,
68         help_text="Reference to the Git repository without definition of the protocol (e.g. https)",
69     )
70     current_commit = models.CharField(
71         max_length=255,
72         blank=True,
73         help_text="Commit hash of the last metadata import. This field is automatically filled by DDI on Rails",
74     )
75     config = JSONField(
76         default=dict, blank=True, null=True, help_text="Configuration of the study (JSON)"
77     )
78 
79     topic_languages = ArrayField(
80         models.CharField(max_length=200),
81         blank=True,
82         default=list,
83         help_text="Topic languages of the study (Array)",
84     )
85 
86     class Meta:  # pylint: disable=too-few-public-methods
87         """ Django's metadata options """
88 
89         verbose_name_plural = "Studies"
90 
91     class DOR:  # pylint: disable=too-few-public-methods
92         """ ddionrails' metadata options """
93 
94         io_fields = ["name", "label", "description"]
95         id_fields = ["name"]
96 
97     def __str__(self) -> str:
98         """ Returns a string representation using the "name" field """
99         return f"/{self.name}"
100 
101     def get_absolute_url(self) -> str:
102         """ Returns a canonical URL for the model using the "name" field """
103         return reverse("study_detail", kwargs={"study_name": self.name})
104 
105     def import_path(self):
106         path = os.path.join(
107             settings.IMPORT_REPO_PATH, self.name, settings.IMPORT_SUB_DIRECTORY
108         )
109         return path
110 
111     def repo_url(self) -> str:
112         if settings.GIT_PROTOCOL == "https":
113             return f"https://{self.repo}.git"
114         elif settings.GIT_PROTOCOL == "ssh":
115             return f"git@{self.repo}.git"
116         else:
117             raise Exception("Specify a protocol for Git in your settings.")
118 
119     def set_topiclist(self, body: List) -> None:
120         _topiclist, _ = TopicList.objects.get_or_create(study=self)
121         _topiclist.topiclist = body
122         _topiclist.save()
123 
124     def has_topics(self) -> bool:
125         """ Returns True if the study has topics False otherwise (evaluates the length of self.topic_languages) """
126         return len(self.topic_languages) > 0
127 
128     def get_topiclist(self, language: str = "en") -> Optional[List]:
129         """ Returns the list of topics for a given language or None """
130         try:
131             for topiclist in self.topiclist.topiclist:
132                 if topiclist.get("language", "") == language:
133                     return topiclist.get("topics")
134         except TopicList.DoesNotExist:
135             return None
136 
137 
138 def context(request):
139     return dict(all_studies=Study.objects.all().only("name", "label", "description"))
140
```

Path: `ddionrails/base/models.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 
3 """ Model definitions for ddionrails.base app """
4 
5 from __future__ import annotations
6 
7 import pathlib
8 
9 from django.conf import settings
10 from django.db import models
11 
12 
13 class System(models.Model):
14     """ Stores a single system instance """
15 
16     name = settings.SYSTEM_NAME
17     current_commit = models.CharField(max_length=255, blank=True)
18 
19     @staticmethod
20     def repo_url() -> str:
21         """ Returns the system's repo url from the settings """
22         return settings.SYSTEM_REPO_URL
23 
24     def import_path(self) -> pathlib.Path:
25         """ Returns the system's import path """
26         return pathlib.Path(settings.IMPORT_REPO_PATH).joinpath(
27             self.name, settings.IMPORT_SUB_DIRECTORY
28         )
29 
30     @classmethod
31     def get(cls) -> System:
32         """ Returns a single system instance """
33         if cls.objects.count() == 0:
34             system = System()
35             system.save()
36         else:
37             system = System.objects.first()
38         return system
39
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/ddionrails/base/mixins.py b/ddionrails/base/mixins.py
--- a/ddionrails/base/mixins.py
+++ b/ddionrails/base/mixins.py
@@ -2,9 +2,11 @@
 
 """ Mixins for ddionrails.base app """
 
+import pathlib
 from typing import Dict
 
 from django import forms
+from django.conf import settings
 
 from config.helpers import render_markdown
 
@@ -145,6 +147,16 @@
         return "/".join(result)
 
 
+class ImportPathMixin:
+    """ A mixin for models to return an import_path based on their name attribute """
+
+    def import_path(self) -> pathlib.Path:
+        """ Returns the instance's import path """
+        return pathlib.Path(settings.IMPORT_REPO_PATH).joinpath(
+            self.name, settings.IMPORT_SUB_DIRECTORY
+        )
+
+
 class AdminMixin:
     """ A mixin for ModelAdmins to query related models via methods """
 
diff --git a/ddionrails/base/models.py b/ddionrails/base/models.py
--- a/ddionrails/base/models.py
+++ b/ddionrails/base/models.py
@@ -4,13 +4,13 @@
 
 from __future__ import annotations
 
-import pathlib
-
 from django.conf import settings
 from django.db import models
 
+from .mixins import ImportPathMixin
+
 
-class System(models.Model):
+class System(ImportPathMixin, models.Model):
     """ Stores a single system instance """
 
     name = settings.SYSTEM_NAME
@@ -21,12 +21,6 @@
         """ Returns the system's repo url from the settings """
         return settings.SYSTEM_REPO_URL
 
-    def import_path(self) -> pathlib.Path:
-        """ Returns the system's import path """
-        return pathlib.Path(settings.IMPORT_REPO_PATH).joinpath(
-            self.name, settings.IMPORT_SUB_DIRECTORY
-        )
-
     @classmethod
     def get(cls) -> System:
         """ Returns a single system instance """
diff --git a/ddionrails/studies/models.py b/ddionrails/studies/models.py
--- a/ddionrails/studies/models.py
+++ b/ddionrails/studies/models.py
@@ -11,7 +11,7 @@
 from django.urls import reverse
 from model_utils.models import TimeStampedModel
 
-from ddionrails.base.mixins import ModelMixin
+from ddionrails.base.mixins import ImportPathMixin, ModelMixin
 
 
 class TopicList(models.Model):
@@ -35,7 +35,7 @@
     )
 
 
-class Study(ModelMixin, TimeStampedModel):
+class Study(ImportPathMixin, ModelMixin, TimeStampedModel):
     """
     Stores a single study,
     related to :model:`data.Dataset`, :model:`instruments.Instrument`,
@@ -102,12 +102,6 @@
         """ Returns a canonical URL for the model using the "name" field """
         return reverse("study_detail", kwargs={"study_name": self.name})
 
-    def import_path(self):
-        path = os.path.join(
-            settings.IMPORT_REPO_PATH, self.name, settings.IMPORT_SUB_DIRECTORY
-        )
-        return path
-
     def repo_url(self) -> str:
         if settings.GIT_PROTOCOL == "https":
             return f"https://{self.repo}.git"

{"golden_diff": "diff --git a/ddionrails/base/mixins.py b/ddionrails/base/mixins.py\n--- a/ddionrails/base/mixins.py\n+++ b/ddionrails/base/mixins.py\n@@ -2,9 +2,11 @@\n \n \"\"\" Mixins for ddionrails.base app \"\"\"\n \n+import pathlib\n from typing import Dict\n \n from django import forms\n+from django.conf import settings\n \n from config.helpers import render_markdown\n \n@@ -145,6 +147,16 @@\n         return \"/\".join(result)\n \n \n+class ImportPathMixin:\n+    \"\"\" A mixin for models to return an import_path based on their name attribute \"\"\"\n+\n+    def import_path(self) -> pathlib.Path:\n+        \"\"\" Returns the instance's import path \"\"\"\n+        return pathlib.Path(settings.IMPORT_REPO_PATH).joinpath(\n+            self.name, settings.IMPORT_SUB_DIRECTORY\n+        )\n+\n+\n class AdminMixin:\n     \"\"\" A mixin for ModelAdmins to query related models via methods \"\"\"\n \ndiff --git a/ddionrails/base/models.py b/ddionrails/base/models.py\n--- a/ddionrails/base/models.py\n+++ b/ddionrails/base/models.py\n@@ -4,13 +4,13 @@\n \n from __future__ import annotations\n \n-import pathlib\n-\n from django.conf import settings\n from django.db import models\n \n+from .mixins import ImportPathMixin\n+\n \n-class System(models.Model):\n+class System(ImportPathMixin, models.Model):\n     \"\"\" Stores a single system instance \"\"\"\n \n     name = settings.SYSTEM_NAME\n@@ -21,12 +21,6 @@\n         \"\"\" Returns the system's repo url from the settings \"\"\"\n         return settings.SYSTEM_REPO_URL\n \n-    def import_path(self) -> pathlib.Path:\n-        \"\"\" Returns the system's import path \"\"\"\n-        return pathlib.Path(settings.IMPORT_REPO_PATH).joinpath(\n-            self.name, settings.IMPORT_SUB_DIRECTORY\n-        )\n-\n     @classmethod\n     def get(cls) -> System:\n         \"\"\" Returns a single system instance \"\"\"\ndiff --git a/ddionrails/studies/models.py b/ddionrails/studies/models.py\n--- a/ddionrails/studies/models.py\n+++ b/ddionrails/studies/models.py\n@@ -11,7 +11,7 @@\n from django.urls import reverse\n from model_utils.models import TimeStampedModel\n \n-from ddionrails.base.mixins import ModelMixin\n+from ddionrails.base.mixins import ImportPathMixin, ModelMixin\n \n \n class TopicList(models.Model):\n@@ -35,7 +35,7 @@\n     )\n \n \n-class Study(ModelMixin, TimeStampedModel):\n+class Study(ImportPathMixin, ModelMixin, TimeStampedModel):\n     \"\"\"\n     Stores a single study,\n     related to :model:`data.Dataset`, :model:`instruments.Instrument`,\n@@ -102,12 +102,6 @@\n         \"\"\" Returns a canonical URL for the model using the \"name\" field \"\"\"\n         return reverse(\"study_detail\", kwargs={\"study_name\": self.name})\n \n-    def import_path(self):\n-        path = os.path.join(\n-            settings.IMPORT_REPO_PATH, self.name, settings.IMPORT_SUB_DIRECTORY\n-        )\n-        return path\n-\n     def repo_url(self) -> str:\n         if settings.GIT_PROTOCOL == \"https\":\n             return f\"https://{self.repo}.git\"\n", "issue": "Move import_path() method into mixin\n### Subject of the issue\r\n\r\nThe System and Study model both implement the same `import_path()` method.\r\n\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n\n\"\"\" Mixins for ddionrails.base app \"\"\"\n\nfrom typing import Dict\n\nfrom django import forms\n\nfrom config.helpers import render_markdown\n\n\nclass ModelMixin:\n    \"\"\"\n    Default mixins for all classes in DDI on Rails.\n\n    Requires two definition in the ``DOR`` class:\n\n    * io_fields: Fields that are used for the default form and in the default dict.\n    * id_fields: Fields that are used for the get_or_create default method.\n\n    Example:\n\n    ::\n\n        from django.db import models\n        from ddionrails.mixins import ModelMixin\n\n        class Test(models.Model, ModelMixin):\n\n            name = models.CharField(max_length=255, unique=True)\n\n            class DOR:\n                id_fields = [\"name\"]\n                io_fields = [\"name\"]\n\n    The default value for DOR is:\n\n    ::\n\n        class DOR:\n            id_fields = [\"name\"]\n            io_fields = [\"name\", \"label\", \"description\"]\n\n    The ``id_fields`` are also use to construct a default string identifier.\n    It is therefore recommended, to order them from the most general to the\n    most specific one.\n\n    \"\"\"\n\n    class DOR:\n        id_fields = [\"name\"]\n        io_fields = [\"name\", \"label\", \"description\"]\n\n    @classmethod\n    def get_or_create(cls, parameters: Dict, lower_strings: bool = True):\n        \"\"\"\n        Default for the get_or_create based on a dict.\n\n        The method uses only relevant identifiers based on ``DOR.id_fields``.\n\n        By default, all strings are set to lower case (option ``lower_strings``).\n        \"\"\"\n        definition = {key: parameters[key] for key in cls.DOR.id_fields}\n        for key, value in definition.items():\n            if value.__class__ == str and lower_strings:\n                definition[key] = value.lower()\n        return cls.objects.get_or_create(**definition)[0]\n\n    @classmethod\n    def get(cls, parameters: Dict):\n        \"\"\"\n        Default for the get_or_create based on a dict.\n\n        The method uses only relevant identifiers based on ``DOR.id_fields``.\n        \"\"\"\n        try:\n            definition = {key: parameters[key] for key in cls.DOR.id_fields}\n            result = cls.objects.get(**definition)\n        except cls.DoesNotExist:\n            result = None\n        return result\n\n    @classmethod\n    def default_form(cls):\n        \"\"\"\n        Creates a default form for all attributes defined in ``DOR.io_fields``.\n        \"\"\"\n\n        class DefaultForm(forms.ModelForm):\n            class Meta:\n                model = cls\n                fields = cls.DOR.io_fields\n\n        return DefaultForm\n\n    def to_dict(self) -> Dict:\n        \"\"\"\n        Uses the ``DOR.io_fields`` attribute to generate a default\n        dict object for the current instance.\n        \"\"\"\n        dictionary = dict()\n        for field in self.DOR.io_fields:\n            value = getattr(self, field)\n            try:\n                dictionary[field] = value.pk\n            except AttributeError:\n                dictionary[field] = value\n        return dictionary\n\n    def title(self):\n        \"\"\"\n        Default for the title. It first looks for a valid label, next for a\n        valid name, and otherwise returns an empty string.\n        \"\"\"\n        try:\n            name = self.name\n        except AttributeError:\n            name = \"\"\n        try:\n            label = self.label\n        except AttributeError:\n            label = \"\"\n        return name if label == \"\" else label\n\n    def html_description(self):\n        \"\"\"\n        Uses the ddionrails Markdown parser (ddionrails.helpers) to render\n        the description into HTML.\n        \"\"\"\n        try:\n            html = render_markdown(self.description)\n        except AttributeError:\n            html = \"\"\n        return html\n\n    def __str__(self):\n        \"\"\" Returns a string reprensentation of the instance, using DOR.id_fields \"\"\"\n        result = []\n        for field in self.DOR.id_fields:\n            value = getattr(self, field)\n            try:\n                result.append(value.string_id())\n            except AttributeError:\n                result.append(str(value))\n        return \"/\".join(result)\n\n\nclass AdminMixin:\n    \"\"\" A mixin for ModelAdmins to query related models via methods \"\"\"\n\n    @staticmethod\n    def study_name(obj):\n        \"\"\" Return the name of the related study \"\"\"\n        try:\n            return obj.study.name\n        except AttributeError:\n            return None\n\n    @staticmethod\n    def period_name(obj):\n        \"\"\" Return the name of the related period \"\"\"\n        try:\n            return obj.period.name\n        except AttributeError:\n            return None\n\n    @staticmethod\n    def analysis_unit_name(obj):\n        \"\"\" Return the name of the related analysis_unit \"\"\"\n        try:\n            return obj.analysis_unit.name\n        except AttributeError:\n            return None\n\n    @staticmethod\n    def dataset_name(obj):\n        \"\"\" Return the name of the related dataset \"\"\"\n        try:\n            return obj.dataset.name\n        except AttributeError:\n            return None\n\n    @staticmethod\n    def dataset_study_name(obj):\n        \"\"\" Return the name of the related dataset.study \"\"\"\n        try:\n            return obj.dataset.study.name\n        except AttributeError:\n            return None\n\n    @staticmethod\n    def instrument_name(obj):\n        \"\"\" Return the name of the related instrument \"\"\"\n        try:\n            return obj.instrument.name\n        except AttributeError:\n            return None\n\n    @staticmethod\n    def instrument_study_name(obj):\n        \"\"\" Return the name of the related instrument.study \"\"\"\n        try:\n            return obj.instrument.study.name\n        except AttributeError:\n            return None\n\n    @staticmethod\n    def basket_name(obj):\n        \"\"\" Return the name of the related basket \"\"\"\n        try:\n            return obj.basket.name\n        except AttributeError:\n            return None\n\n    @staticmethod\n    def basket_study_name(obj):\n        \"\"\" Return the name of the related basket.study \"\"\"\n        try:\n            return obj.basket.study.name\n        except AttributeError:\n            return None\n\n    @staticmethod\n    def user_name(obj):\n        \"\"\" Return the name of the related basket.user \"\"\"\n        try:\n            return obj.basket.user.username\n        except AttributeError:\n            return None\n", "path": "ddionrails/base/mixins.py"}, {"content": "# -*- coding: utf-8 -*-\n\"\"\" Model definitions for ddionrails.studies app \"\"\"\n\nimport os\nfrom typing import List, Optional\n\nfrom django.conf import settings\nfrom django.contrib.postgres.fields import ArrayField, JSONField\nfrom django.contrib.postgres.fields.jsonb import JSONField as JSONBField\nfrom django.db import models\nfrom django.urls import reverse\nfrom model_utils.models import TimeStampedModel\n\nfrom ddionrails.base.mixins import ModelMixin\n\n\nclass TopicList(models.Model):\n\n    # attributes\n    topiclist = JSONBField(\n        default=list,\n        null=True,\n        blank=True,\n        help_text=\"Topics of the related study (JSON)\",\n    )\n\n    # relations\n    study = models.OneToOneField(\n        \"Study\",\n        blank=True,\n        null=True,\n        related_name=\"topiclist\",\n        on_delete=models.CASCADE,\n        help_text=\"OneToOneField to studies.Study\",\n    )\n\n\nclass Study(ModelMixin, TimeStampedModel):\n    \"\"\"\n    Stores a single study,\n    related to :model:`data.Dataset`, :model:`instruments.Instrument`,\n    :model:`concepts.Period` and :model:`workspace.Basket`.\n    \"\"\"\n\n    # attributes\n    name = models.CharField(\n        max_length=255, unique=True, db_index=True, help_text=\"Name of the study\"\n    )\n    label = models.CharField(\n        max_length=255,\n        blank=True,\n        verbose_name=\"Label (English)\",\n        help_text=\"Label of the study (English)\",\n    )\n    label_de = models.CharField(\n        max_length=255,\n        blank=True,\n        null=True,\n        verbose_name=\"Label (German)\",\n        help_text=\"Label of the study (German)\",\n    )\n    description = models.TextField(\n        blank=True, help_text=\"Description of the study (Markdown)\"\n    )\n    repo = models.CharField(\n        max_length=255,\n        blank=True,\n        help_text=\"Reference to the Git repository without definition of the protocol (e.g. https)\",\n    )\n    current_commit = models.CharField(\n        max_length=255,\n        blank=True,\n        help_text=\"Commit hash of the last metadata import. This field is automatically filled by DDI on Rails\",\n    )\n    config = JSONField(\n        default=dict, blank=True, null=True, help_text=\"Configuration of the study (JSON)\"\n    )\n\n    topic_languages = ArrayField(\n        models.CharField(max_length=200),\n        blank=True,\n        default=list,\n        help_text=\"Topic languages of the study (Array)\",\n    )\n\n    class Meta:  # pylint: disable=too-few-public-methods\n        \"\"\" Django's metadata options \"\"\"\n\n        verbose_name_plural = \"Studies\"\n\n    class DOR:  # pylint: disable=too-few-public-methods\n        \"\"\" ddionrails' metadata options \"\"\"\n\n        io_fields = [\"name\", \"label\", \"description\"]\n        id_fields = [\"name\"]\n\n    def __str__(self) -> str:\n        \"\"\" Returns a string representation using the \"name\" field \"\"\"\n        return f\"/{self.name}\"\n\n    def get_absolute_url(self) -> str:\n        \"\"\" Returns a canonical URL for the model using the \"name\" field \"\"\"\n        return reverse(\"study_detail\", kwargs={\"study_name\": self.name})\n\n    def import_path(self):\n        path = os.path.join(\n            settings.IMPORT_REPO_PATH, self.name, settings.IMPORT_SUB_DIRECTORY\n        )\n        return path\n\n    def repo_url(self) -> str:\n        if settings.GIT_PROTOCOL == \"https\":\n            return f\"https://{self.repo}.git\"\n        elif settings.GIT_PROTOCOL == \"ssh\":\n            return f\"git@{self.repo}.git\"\n        else:\n            raise Exception(\"Specify a protocol for Git in your settings.\")\n\n    def set_topiclist(self, body: List) -> None:\n        _topiclist, _ = TopicList.objects.get_or_create(study=self)\n        _topiclist.topiclist = body\n        _topiclist.save()\n\n    def has_topics(self) -> bool:\n        \"\"\" Returns True if the study has topics False otherwise (evaluates the length of self.topic_languages) \"\"\"\n        return len(self.topic_languages) > 0\n\n    def get_topiclist(self, language: str = \"en\") -> Optional[List]:\n        \"\"\" Returns the list of topics for a given language or None \"\"\"\n        try:\n            for topiclist in self.topiclist.topiclist:\n                if topiclist.get(\"language\", \"\") == language:\n                    return topiclist.get(\"topics\")\n        except TopicList.DoesNotExist:\n            return None\n\n\ndef context(request):\n    return dict(all_studies=Study.objects.all().only(\"name\", \"label\", \"description\"))\n", "path": "ddionrails/studies/models.py"}, {"content": "# -*- coding: utf-8 -*-\n\n\"\"\" Model definitions for ddionrails.base app \"\"\"\n\nfrom __future__ import annotations\n\nimport pathlib\n\nfrom django.conf import settings\nfrom django.db import models\n\n\nclass System(models.Model):\n    \"\"\" Stores a single system instance \"\"\"\n\n    name = settings.SYSTEM_NAME\n    current_commit = models.CharField(max_length=255, blank=True)\n\n    @staticmethod\n    def repo_url() -> str:\n        \"\"\" Returns the system's repo url from the settings \"\"\"\n        return settings.SYSTEM_REPO_URL\n\n    def import_path(self) -> pathlib.Path:\n        \"\"\" Returns the system's import path \"\"\"\n        return pathlib.Path(settings.IMPORT_REPO_PATH).joinpath(\n            self.name, settings.IMPORT_SUB_DIRECTORY\n        )\n\n    @classmethod\n    def get(cls) -> System:\n        \"\"\" Returns a single system instance \"\"\"\n        if cls.objects.count() == 0:\n            system = System()\n            system.save()\n        else:\n            system = System.objects.first()\n        return system\n", "path": "ddionrails/base/models.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n\n\"\"\" Mixins for ddionrails.base app \"\"\"\n\nimport pathlib\nfrom typing import Dict\n\nfrom django import forms\nfrom django.conf import settings\n\nfrom config.helpers import render_markdown\n\n\nclass ModelMixin:\n    \"\"\"\n    Default mixins for all classes in DDI on Rails.\n\n    Requires two definition in the ``DOR`` class:\n\n    * io_fields: Fields that are used for the default form and in the default dict.\n    * id_fields: Fields that are used for the get_or_create default method.\n\n    Example:\n\n    ::\n\n        from django.db import models\n        from ddionrails.mixins import ModelMixin\n\n        class Test(models.Model, ModelMixin):\n\n            name = models.CharField(max_length=255, unique=True)\n\n            class DOR:\n                id_fields = [\"name\"]\n                io_fields = [\"name\"]\n\n    The default value for DOR is:\n\n    ::\n\n        class DOR:\n            id_fields = [\"name\"]\n            io_fields = [\"name\", \"label\", \"description\"]\n\n    The ``id_fields`` are also use to construct a default string identifier.\n    It is therefore recommended, to order them from the most general to the\n    most specific one.\n\n    \"\"\"\n\n    class DOR:\n        id_fields = [\"name\"]\n        io_fields = [\"name\", \"label\", \"description\"]\n\n    @classmethod\n    def get_or_create(cls, parameters: Dict, lower_strings: bool = True):\n        \"\"\"\n        Default for the get_or_create based on a dict.\n\n        The method uses only relevant identifiers based on ``DOR.id_fields``.\n\n        By default, all strings are set to lower case (option ``lower_strings``).\n        \"\"\"\n        definition = {key: parameters[key] for key in cls.DOR.id_fields}\n        for key, value in definition.items():\n            if value.__class__ == str and lower_strings:\n                definition[key] = value.lower()\n        return cls.objects.get_or_create(**definition)[0]\n\n    @classmethod\n    def get(cls, parameters: Dict):\n        \"\"\"\n        Default for the get_or_create based on a dict.\n\n        The method uses only relevant identifiers based on ``DOR.id_fields``.\n        \"\"\"\n        try:\n            definition = {key: parameters[key] for key in cls.DOR.id_fields}\n            result = cls.objects.get(**definition)\n        except cls.DoesNotExist:\n            result = None\n        return result\n\n    @classmethod\n    def default_form(cls):\n        \"\"\"\n        Creates a default form for all attributes defined in ``DOR.io_fields``.\n        \"\"\"\n\n        class DefaultForm(forms.ModelForm):\n            class Meta:\n                model = cls\n                fields = cls.DOR.io_fields\n\n        return DefaultForm\n\n    def to_dict(self) -> Dict:\n        \"\"\"\n        Uses the ``DOR.io_fields`` attribute to generate a default\n        dict object for the current instance.\n        \"\"\"\n        dictionary = dict()\n        for field in self.DOR.io_fields:\n            value = getattr(self, field)\n            try:\n                dictionary[field] = value.pk\n            except AttributeError:\n                dictionary[field] = value\n        return dictionary\n\n    def title(self):\n        \"\"\"\n        Default for the title. It first looks for a valid label, next for a\n        valid name, and otherwise returns an empty string.\n        \"\"\"\n        try:\n            name = self.name\n        except AttributeError:\n            name = \"\"\n        try:\n            label = self.label\n        except AttributeError:\n            label = \"\"\n        return name if label == \"\" else label\n\n    def html_description(self):\n        \"\"\"\n        Uses the ddionrails Markdown parser (ddionrails.helpers) to render\n        the description into HTML.\n        \"\"\"\n        try:\n            html = render_markdown(self.description)\n        except AttributeError:\n            html = \"\"\n        return html\n\n    def __str__(self):\n        \"\"\" Returns a string reprensentation of the instance, using DOR.id_fields \"\"\"\n        result = []\n        for field in self.DOR.id_fields:\n            value = getattr(self, field)\n            try:\n                result.append(value.string_id())\n            except AttributeError:\n                result.append(str(value))\n        return \"/\".join(result)\n\n\nclass ImportPathMixin:\n    \"\"\" A mixin for models to return an import_path based on their name attribute \"\"\"\n\n    def import_path(self) -> pathlib.Path:\n        \"\"\" Returns the instance's import path \"\"\"\n        return pathlib.Path(settings.IMPORT_REPO_PATH).joinpath(\n            self.name, settings.IMPORT_SUB_DIRECTORY\n        )\n\n\nclass AdminMixin:\n    \"\"\" A mixin for ModelAdmins to query related models via methods \"\"\"\n\n    @staticmethod\n    def study_name(obj):\n        \"\"\" Return the name of the related study \"\"\"\n        try:\n            return obj.study.name\n        except AttributeError:\n            return None\n\n    @staticmethod\n    def period_name(obj):\n        \"\"\" Return the name of the related period \"\"\"\n        try:\n            return obj.period.name\n        except AttributeError:\n            return None\n\n    @staticmethod\n    def analysis_unit_name(obj):\n        \"\"\" Return the name of the related analysis_unit \"\"\"\n        try:\n            return obj.analysis_unit.name\n        except AttributeError:\n            return None\n\n    @staticmethod\n    def dataset_name(obj):\n        \"\"\" Return the name of the related dataset \"\"\"\n        try:\n            return obj.dataset.name\n        except AttributeError:\n            return None\n\n    @staticmethod\n    def dataset_study_name(obj):\n        \"\"\" Return the name of the related dataset.study \"\"\"\n        try:\n            return obj.dataset.study.name\n        except AttributeError:\n            return None\n\n    @staticmethod\n    def instrument_name(obj):\n        \"\"\" Return the name of the related instrument \"\"\"\n        try:\n            return obj.instrument.name\n        except AttributeError:\n            return None\n\n    @staticmethod\n    def instrument_study_name(obj):\n        \"\"\" Return the name of the related instrument.study \"\"\"\n        try:\n            return obj.instrument.study.name\n        except AttributeError:\n            return None\n\n    @staticmethod\n    def basket_name(obj):\n        \"\"\" Return the name of the related basket \"\"\"\n        try:\n            return obj.basket.name\n        except AttributeError:\n            return None\n\n    @staticmethod\n    def basket_study_name(obj):\n        \"\"\" Return the name of the related basket.study \"\"\"\n        try:\n            return obj.basket.study.name\n        except AttributeError:\n            return None\n\n    @staticmethod\n    def user_name(obj):\n        \"\"\" Return the name of the related basket.user \"\"\"\n        try:\n            return obj.basket.user.username\n        except AttributeError:\n            return None\n", "path": "ddionrails/base/mixins.py"}, {"content": "# -*- coding: utf-8 -*-\n\"\"\" Model definitions for ddionrails.studies app \"\"\"\n\nimport os\nfrom typing import List, Optional\n\nfrom django.conf import settings\nfrom django.contrib.postgres.fields import ArrayField, JSONField\nfrom django.contrib.postgres.fields.jsonb import JSONField as JSONBField\nfrom django.db import models\nfrom django.urls import reverse\nfrom model_utils.models import TimeStampedModel\n\nfrom ddionrails.base.mixins import ImportPathMixin, ModelMixin\n\n\nclass TopicList(models.Model):\n\n    # attributes\n    topiclist = JSONBField(\n        default=list,\n        null=True,\n        blank=True,\n        help_text=\"Topics of the related study (JSON)\",\n    )\n\n    # relations\n    study = models.OneToOneField(\n        \"Study\",\n        blank=True,\n        null=True,\n        related_name=\"topiclist\",\n        on_delete=models.CASCADE,\n        help_text=\"OneToOneField to studies.Study\",\n    )\n\n\nclass Study(ImportPathMixin, ModelMixin, TimeStampedModel):\n    \"\"\"\n    Stores a single study,\n    related to :model:`data.Dataset`, :model:`instruments.Instrument`,\n    :model:`concepts.Period` and :model:`workspace.Basket`.\n    \"\"\"\n\n    # attributes\n    name = models.CharField(\n        max_length=255, unique=True, db_index=True, help_text=\"Name of the study\"\n    )\n    label = models.CharField(\n        max_length=255,\n        blank=True,\n        verbose_name=\"Label (English)\",\n        help_text=\"Label of the study (English)\",\n    )\n    label_de = models.CharField(\n        max_length=255,\n        blank=True,\n        null=True,\n        verbose_name=\"Label (German)\",\n        help_text=\"Label of the study (German)\",\n    )\n    description = models.TextField(\n        blank=True, help_text=\"Description of the study (Markdown)\"\n    )\n    repo = models.CharField(\n        max_length=255,\n        blank=True,\n        help_text=\"Reference to the Git repository without definition of the protocol (e.g. https)\",\n    )\n    current_commit = models.CharField(\n        max_length=255,\n        blank=True,\n        help_text=\"Commit hash of the last metadata import. This field is automatically filled by DDI on Rails\",\n    )\n    config = JSONField(\n        default=dict, blank=True, null=True, help_text=\"Configuration of the study (JSON)\"\n    )\n\n    topic_languages = ArrayField(\n        models.CharField(max_length=200),\n        blank=True,\n        default=list,\n        help_text=\"Topic languages of the study (Array)\",\n    )\n\n    class Meta:  # pylint: disable=too-few-public-methods\n        \"\"\" Django's metadata options \"\"\"\n\n        verbose_name_plural = \"Studies\"\n\n    class DOR:  # pylint: disable=too-few-public-methods\n        \"\"\" ddionrails' metadata options \"\"\"\n\n        io_fields = [\"name\", \"label\", \"description\"]\n        id_fields = [\"name\"]\n\n    def __str__(self) -> str:\n        \"\"\" Returns a string representation using the \"name\" field \"\"\"\n        return f\"/{self.name}\"\n\n    def get_absolute_url(self) -> str:\n        \"\"\" Returns a canonical URL for the model using the \"name\" field \"\"\"\n        return reverse(\"study_detail\", kwargs={\"study_name\": self.name})\n\n    def repo_url(self) -> str:\n        if settings.GIT_PROTOCOL == \"https\":\n            return f\"https://{self.repo}.git\"\n        elif settings.GIT_PROTOCOL == \"ssh\":\n            return f\"git@{self.repo}.git\"\n        else:\n            raise Exception(\"Specify a protocol for Git in your settings.\")\n\n    def set_topiclist(self, body: List) -> None:\n        _topiclist, _ = TopicList.objects.get_or_create(study=self)\n        _topiclist.topiclist = body\n        _topiclist.save()\n\n    def has_topics(self) -> bool:\n        \"\"\" Returns True if the study has topics False otherwise (evaluates the length of self.topic_languages) \"\"\"\n        return len(self.topic_languages) > 0\n\n    def get_topiclist(self, language: str = \"en\") -> Optional[List]:\n        \"\"\" Returns the list of topics for a given language or None \"\"\"\n        try:\n            for topiclist in self.topiclist.topiclist:\n                if topiclist.get(\"language\", \"\") == language:\n                    return topiclist.get(\"topics\")\n        except TopicList.DoesNotExist:\n            return None\n\n\ndef context(request):\n    return dict(all_studies=Study.objects.all().only(\"name\", \"label\", \"description\"))\n", "path": "ddionrails/studies/models.py"}, {"content": "# -*- coding: utf-8 -*-\n\n\"\"\" Model definitions for ddionrails.base app \"\"\"\n\nfrom __future__ import annotations\n\nfrom django.conf import settings\nfrom django.db import models\n\nfrom .mixins import ImportPathMixin\n\n\nclass System(ImportPathMixin, models.Model):\n    \"\"\" Stores a single system instance \"\"\"\n\n    name = settings.SYSTEM_NAME\n    current_commit = models.CharField(max_length=255, blank=True)\n\n    @staticmethod\n    def repo_url() -> str:\n        \"\"\" Returns the system's repo url from the settings \"\"\"\n        return settings.SYSTEM_REPO_URL\n\n    @classmethod\n    def get(cls) -> System:\n        \"\"\" Returns a single system instance \"\"\"\n        if cls.objects.count() == 0:\n            system = System()\n            system.save()\n        else:\n            system = System.objects.first()\n        return system\n", "path": "ddionrails/base/models.py"}]}