We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Sub-optimal performance during exports due to N+1 queries and complex quries
**Version**
Please provide the versions of the pulpcore and plugin packages in use, and how they are installed.  If you are using Pulp via Katello, please provide the Katello version.

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:

**Expected behavior**
A clear and concise description of what you expected to happen.

**Additional context**
Add any other context about the problem here.  Please provide links to any previous discussions via Discourse or Bugzilla.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `pulpcore/app/viewsets/importer.py`
Content:
```
1 from django.http import Http404
2 from django_filters.rest_framework import filters
3 from drf_spectacular.utils import extend_schema
4 from rest_framework import mixins
5 
6 from pulpcore.app.models import (
7     Import,
8     Importer,
9     PulpImport,
10     PulpImporter,
11     TaskGroup,
12 )
13 from pulpcore.app.response import TaskGroupOperationResponse
14 from pulpcore.app.serializers import (
15     ImportSerializer,
16     ImporterSerializer,
17     PulpImporterSerializer,
18     PulpImportSerializer,
19     TaskGroupOperationResponseSerializer,
20 )
21 from pulpcore.app.tasks import pulp_import
22 from pulpcore.app.viewsets import (
23     BaseFilterSet,
24     NamedModelViewSet,
25 )
26 from pulpcore.app.viewsets.base import NAME_FILTER_OPTIONS
27 from pulpcore.tasking.tasks import dispatch
28 
29 
30 class ImporterFilter(BaseFilterSet):
31     """Filter for Importers."""
32 
33     name = filters.CharFilter()
34 
35     class Meta:
36         model = Importer
37         fields = {
38             "name": NAME_FILTER_OPTIONS,
39         }
40 
41 
42 class ImporterViewSet(
43     NamedModelViewSet,
44     mixins.CreateModelMixin,
45     mixins.UpdateModelMixin,
46     mixins.RetrieveModelMixin,
47     mixins.ListModelMixin,
48     mixins.DestroyModelMixin,
49 ):
50     """ViewSet for Importers."""
51 
52     queryset = Importer.objects.all()
53     serializer_class = ImporterSerializer
54     endpoint_name = "importers"
55     router_lookup = "importer"
56     filterset_class = ImporterFilter
57 
58 
59 class ImportViewSet(
60     NamedModelViewSet,
61     mixins.CreateModelMixin,
62     mixins.RetrieveModelMixin,
63     mixins.ListModelMixin,
64     mixins.DestroyModelMixin,
65 ):
66     """ViewSet for viewing imports from an Importer."""
67 
68     endpoint_name = "imports"
69     nest_prefix = "importers"
70     router_lookup = "import"
71     lookup_field = "pk"
72     parent_viewset = ImporterViewSet
73     parent_lookup_kwargs = {"importer_pk": "importer__pk"}
74     serializer_class = ImportSerializer
75     queryset = Import.objects.all()
76 
77 
78 class PulpImporterViewSet(ImporterViewSet):
79     """ViewSet for PulpImporters."""
80 
81     endpoint_name = "pulp"
82     serializer_class = PulpImporterSerializer
83     queryset = PulpImporter.objects.all()
84 
85 
86 class PulpImportViewSet(ImportViewSet):
87     """ViewSet for PulpImports."""
88 
89     parent_viewset = PulpImporterViewSet
90     queryset = PulpImport.objects.all()
91 
92     @extend_schema(
93         request=PulpImportSerializer,
94         description="Trigger an asynchronous task to import a Pulp export.",
95         responses={202: TaskGroupOperationResponseSerializer},
96     )
97     def create(self, request, importer_pk):
98         """Import a Pulp export into Pulp."""
99         try:
100             importer = PulpImporter.objects.get(pk=importer_pk)
101         except PulpImporter.DoesNotExist:
102             raise Http404
103 
104         serializer = PulpImportSerializer(data=request.data, context={"request": request})
105         serializer.is_valid(raise_exception=True)
106 
107         path = serializer.validated_data.get("path")
108         toc = serializer.validated_data.get("toc")
109         task_group = TaskGroup.objects.create(description=f"Import of {path}")
110 
111         dispatch(
112             pulp_import,
113             exclusive_resources=[importer],
114             task_group=task_group,
115             kwargs={"importer_pk": importer.pk, "path": path, "toc": toc},
116         )
117         return TaskGroupOperationResponse(task_group, request)
118
```

Path: `pulpcore/app/modelresource.py`
Content:
```
1 from import_export import fields
2 from import_export.widgets import ForeignKeyWidget
3 from logging import getLogger
4 
5 from pulpcore.app.models.content import (
6     Artifact,
7     Content,
8     ContentArtifact,
9 )
10 from pulpcore.app.models.repository import Repository
11 from pulpcore.constants import ALL_KNOWN_CONTENT_CHECKSUMS
12 from pulpcore.plugin.importexport import QueryModelResource
13 
14 
15 log = getLogger(__name__)
16 
17 
18 #
19 # Artifact and Repository are different from other import-export entities, in that they are not
20 # repo-version-specific.
21 #
22 class ArtifactResource(QueryModelResource):
23     """Resource for import/export of artifacts."""
24 
25     def before_import_row(self, row, **kwargs):
26         """
27         Sets digests to None if they are blank strings.
28 
29         Args:
30             row (tablib.Dataset row): incoming import-row representing a single Variant.
31             kwargs: args passed along from the import() call.
32 
33         """
34         # the export converts None to blank strings but sha384 and sha512 have unique constraints
35         # that get triggered if they are blank. convert checksums back into None if they are blank.
36         for checksum in ALL_KNOWN_CONTENT_CHECKSUMS:
37             if row[checksum] == "":
38                 row[checksum] = None
39 
40     class Meta:
41         model = Artifact
42         exclude = (
43             "pulp_id",
44             "pulp_created",
45             "pulp_last_updated",
46         )
47         import_id_fields = ("sha256",)
48 
49 
50 class RepositoryResource(QueryModelResource):
51     class Meta:
52         model = Repository
53         import_id_fields = ("name",)
54         exclude = (
55             "pulp_id",
56             "pulp_created",
57             "pulp_last_updated",
58             "content",
59         )
60 
61 
62 class ContentArtifactResource(QueryModelResource):
63     """
64     Handles import/export of the ContentArtifact model.
65 
66     ContentArtifact is different from other import-export entities because it has no 'natural key'
67     other than a pulp_id, which aren't shared across instances. We do some magic to link up
68     ContentArtifacts to their matching (already-imported) Content.
69 
70     Some plugin-models have sub-repositories. We take advantage of the content-mapping
71     machinery to account for those contentartifacts as well.
72     """
73 
74     artifact = fields.Field(
75         column_name="artifact", attribute="artifact", widget=ForeignKeyWidget(Artifact, "sha256")
76     )
77 
78     def __init__(self, repo_version=None, content_mapping=None):
79         self.content_mapping = content_mapping
80         super().__init__(repo_version)
81 
82     def before_import_row(self, row, **kwargs):
83         """
84         Fixes the content-ptr of an incoming content-artifact row at import time.
85 
86         Finds the 'original uuid' of the Content for this row, looks it up as the
87         'upstream_id' of imported Content, and then replaces the Content-pk with its
88         (new) uuid.
89 
90         Args:
91             row (tablib.Dataset row): incoming import-row representing a single ContentArtifact.
92             kwargs: args passed along from the import() call.
93 
94         Returns:
95             (tablib.Dataset row): row that now points to the new downstream uuid for its content.
96         """
97 
98         linked_content = Content.objects.get(upstream_id=row["content"])
99         row["content"] = str(linked_content.pulp_id)
100 
101     def set_up_queryset(self):
102         vers_content = ContentArtifact.objects.filter(content__in=self.repo_version.content)
103         if self.content_mapping:
104             all_content = []
105             for content_ids in self.content_mapping.values():
106                 all_content.extend(content_ids)
107             vers_content = vers_content.union(
108                 ContentArtifact.objects.filter(content__in=all_content)
109             )
110         return vers_content.order_by("content", "relative_path")
111 
112     class Meta:
113         model = ContentArtifact
114         import_id_fields = (
115             "content",
116             "relative_path",
117         )
118         exclude = (
119             "pulp_created",
120             "pulp_last_updated",
121             "_artifacts",
122             "pulp_id",
123         )
124
```

Path: `pulpcore/plugin/importexport.py`
Content:
```
1 from import_export import resources
2 
3 
4 class QueryModelResource(resources.ModelResource):
5     """
6     A ModelResource that knows the RepositoryVersion to use to filter its query
7 
8     QueryModelResource has-a repository-version that can be used to limit its export, and a
9     queryset that is derived from that repository-version.
10 
11     A plugin-writer will subclass their ModelResources from QueryModelResource,
12     and use it to define the limiting query
13 
14     Attributes:
15 
16         repo_version (models.RepositoryVersion): The RepositoryVersion whose content we would like
17             to export
18         queryset (django.db.models.query.QuerySet): filtering queryset for this resource
19             (driven by repo_version)
20     """
21 
22     def set_up_queryset(self):
23         return None
24 
25     def __init__(self, repo_version=None):
26         self.repo_version = repo_version
27         if repo_version:
28             self.queryset = self.set_up_queryset()
29 
30     class Meta:
31         exclude = ("pulp_id", "pulp_created", "pulp_last_updated")
32 
33 
34 class BaseContentResource(QueryModelResource):
35     """
36     A QueryModelResource that knows how to fill in the 'upstream_id' export-field
37 
38     BaseContentResource knows to de/hydrate upstream_id with the content-being-exported's pulp_id.
39 
40     All Content-based resources being import/exported should subclass from this class.
41     """
42 
43     # An optional mapping that maps Content to Repositories. Useful when Content is sometimes not
44     # tied directly to a Repository but rather to a subrepo. Formatting:
45     #
46     #     {"<repo name>": ["<content upstream_id>", "..."]}
47     #
48     content_mapping = None
49 
50     class Meta:
51         exclude = QueryModelResource.Meta.exclude + ("_artifacts", "content", "content_ptr")
52 
53     def dehydrate_upstream_id(self, content):
54         return str(content.pulp_id)
55
```
--- 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/pulpcore/app/modelresource.py b/pulpcore/app/modelresource.py
--- a/pulpcore/app/modelresource.py
+++ b/pulpcore/app/modelresource.py
@@ -43,6 +43,7 @@
             "pulp_id",
             "pulp_created",
             "pulp_last_updated",
+            "timestamp_of_interest",
         )
         import_id_fields = ("sha256",)
 
@@ -99,15 +100,20 @@
         row["content"] = str(linked_content.pulp_id)
 
     def set_up_queryset(self):
-        vers_content = ContentArtifact.objects.filter(content__in=self.repo_version.content)
+        content_pks = set(self.repo_version.content.values_list("pk", flat=True))
+
         if self.content_mapping:
-            all_content = []
             for content_ids in self.content_mapping.values():
-                all_content.extend(content_ids)
-            vers_content = vers_content.union(
-                ContentArtifact.objects.filter(content__in=all_content)
-            )
-        return vers_content.order_by("content", "relative_path")
+                content_pks |= set(content_ids)
+
+        return (
+            ContentArtifact.objects.filter(content__in=content_pks)
+            .order_by("content", "relative_path")
+            .select_related("artifact")
+        )
+
+    def dehydrate_content(self, content_artifact):
+        return str(content_artifact.content_id)
 
     class Meta:
         model = ContentArtifact
diff --git a/pulpcore/app/viewsets/importer.py b/pulpcore/app/viewsets/importer.py
--- a/pulpcore/app/viewsets/importer.py
+++ b/pulpcore/app/viewsets/importer.py
@@ -106,7 +106,9 @@
 
         path = serializer.validated_data.get("path")
         toc = serializer.validated_data.get("toc")
-        task_group = TaskGroup.objects.create(description=f"Import of {path}")
+        task_group = TaskGroup.objects.create(
+            description="Import of {path}".format(path=path or toc)
+        )
 
         dispatch(
             pulp_import,
diff --git a/pulpcore/plugin/importexport.py b/pulpcore/plugin/importexport.py
--- a/pulpcore/plugin/importexport.py
+++ b/pulpcore/plugin/importexport.py
@@ -22,6 +22,9 @@
     def set_up_queryset(self):
         return None
 
+    def dehydrate_pulp_domain(self, content):
+        return str(content.pulp_domain_id)
+
     def __init__(self, repo_version=None):
         self.repo_version = repo_version
         if repo_version:
@@ -47,8 +50,13 @@
     #
     content_mapping = None
 
-    class Meta:
-        exclude = QueryModelResource.Meta.exclude + ("_artifacts", "content", "content_ptr")
-
     def dehydrate_upstream_id(self, content):
         return str(content.pulp_id)
+
+    class Meta:
+        exclude = QueryModelResource.Meta.exclude + (
+            "_artifacts",
+            "content",
+            "content_ptr",
+            "timestamp_of_interest",
+        )

{"golden_diff": "diff --git a/pulpcore/app/modelresource.py b/pulpcore/app/modelresource.py\n--- a/pulpcore/app/modelresource.py\n+++ b/pulpcore/app/modelresource.py\n@@ -43,6 +43,7 @@\n             \"pulp_id\",\n             \"pulp_created\",\n             \"pulp_last_updated\",\n+            \"timestamp_of_interest\",\n         )\n         import_id_fields = (\"sha256\",)\n \n@@ -99,15 +100,20 @@\n         row[\"content\"] = str(linked_content.pulp_id)\n \n     def set_up_queryset(self):\n-        vers_content = ContentArtifact.objects.filter(content__in=self.repo_version.content)\n+        content_pks = set(self.repo_version.content.values_list(\"pk\", flat=True))\n+\n         if self.content_mapping:\n-            all_content = []\n             for content_ids in self.content_mapping.values():\n-                all_content.extend(content_ids)\n-            vers_content = vers_content.union(\n-                ContentArtifact.objects.filter(content__in=all_content)\n-            )\n-        return vers_content.order_by(\"content\", \"relative_path\")\n+                content_pks |= set(content_ids)\n+\n+        return (\n+            ContentArtifact.objects.filter(content__in=content_pks)\n+            .order_by(\"content\", \"relative_path\")\n+            .select_related(\"artifact\")\n+        )\n+\n+    def dehydrate_content(self, content_artifact):\n+        return str(content_artifact.content_id)\n \n     class Meta:\n         model = ContentArtifact\ndiff --git a/pulpcore/app/viewsets/importer.py b/pulpcore/app/viewsets/importer.py\n--- a/pulpcore/app/viewsets/importer.py\n+++ b/pulpcore/app/viewsets/importer.py\n@@ -106,7 +106,9 @@\n \n         path = serializer.validated_data.get(\"path\")\n         toc = serializer.validated_data.get(\"toc\")\n-        task_group = TaskGroup.objects.create(description=f\"Import of {path}\")\n+        task_group = TaskGroup.objects.create(\n+            description=\"Import of {path}\".format(path=path or toc)\n+        )\n \n         dispatch(\n             pulp_import,\ndiff --git a/pulpcore/plugin/importexport.py b/pulpcore/plugin/importexport.py\n--- a/pulpcore/plugin/importexport.py\n+++ b/pulpcore/plugin/importexport.py\n@@ -22,6 +22,9 @@\n     def set_up_queryset(self):\n         return None\n \n+    def dehydrate_pulp_domain(self, content):\n+        return str(content.pulp_domain_id)\n+\n     def __init__(self, repo_version=None):\n         self.repo_version = repo_version\n         if repo_version:\n@@ -47,8 +50,13 @@\n     #\n     content_mapping = None\n \n-    class Meta:\n-        exclude = QueryModelResource.Meta.exclude + (\"_artifacts\", \"content\", \"content_ptr\")\n-\n     def dehydrate_upstream_id(self, content):\n         return str(content.pulp_id)\n+\n+    class Meta:\n+        exclude = QueryModelResource.Meta.exclude + (\n+            \"_artifacts\",\n+            \"content\",\n+            \"content_ptr\",\n+            \"timestamp_of_interest\",\n+        )\n", "issue": "Sub-optimal performance during exports due to N+1 queries and complex quries\n**Version**\r\nPlease provide the versions of the pulpcore and plugin packages in use, and how they are installed.  If you are using Pulp via Katello, please provide the Katello version.\r\n\r\n**Describe the bug**\r\nA clear and concise description of what the bug is.\r\n\r\n**To Reproduce**\r\nSteps to reproduce the behavior:\r\n\r\n**Expected behavior**\r\nA clear and concise description of what you expected to happen.\r\n\r\n**Additional context**\r\nAdd any other context about the problem here.  Please provide links to any previous discussions via Discourse or Bugzilla.\r\n\n", "before_files": [{"content": "from django.http import Http404\nfrom django_filters.rest_framework import filters\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework import mixins\n\nfrom pulpcore.app.models import (\n    Import,\n    Importer,\n    PulpImport,\n    PulpImporter,\n    TaskGroup,\n)\nfrom pulpcore.app.response import TaskGroupOperationResponse\nfrom pulpcore.app.serializers import (\n    ImportSerializer,\n    ImporterSerializer,\n    PulpImporterSerializer,\n    PulpImportSerializer,\n    TaskGroupOperationResponseSerializer,\n)\nfrom pulpcore.app.tasks import pulp_import\nfrom pulpcore.app.viewsets import (\n    BaseFilterSet,\n    NamedModelViewSet,\n)\nfrom pulpcore.app.viewsets.base import NAME_FILTER_OPTIONS\nfrom pulpcore.tasking.tasks import dispatch\n\n\nclass ImporterFilter(BaseFilterSet):\n    \"\"\"Filter for Importers.\"\"\"\n\n    name = filters.CharFilter()\n\n    class Meta:\n        model = Importer\n        fields = {\n            \"name\": NAME_FILTER_OPTIONS,\n        }\n\n\nclass ImporterViewSet(\n    NamedModelViewSet,\n    mixins.CreateModelMixin,\n    mixins.UpdateModelMixin,\n    mixins.RetrieveModelMixin,\n    mixins.ListModelMixin,\n    mixins.DestroyModelMixin,\n):\n    \"\"\"ViewSet for Importers.\"\"\"\n\n    queryset = Importer.objects.all()\n    serializer_class = ImporterSerializer\n    endpoint_name = \"importers\"\n    router_lookup = \"importer\"\n    filterset_class = ImporterFilter\n\n\nclass ImportViewSet(\n    NamedModelViewSet,\n    mixins.CreateModelMixin,\n    mixins.RetrieveModelMixin,\n    mixins.ListModelMixin,\n    mixins.DestroyModelMixin,\n):\n    \"\"\"ViewSet for viewing imports from an Importer.\"\"\"\n\n    endpoint_name = \"imports\"\n    nest_prefix = \"importers\"\n    router_lookup = \"import\"\n    lookup_field = \"pk\"\n    parent_viewset = ImporterViewSet\n    parent_lookup_kwargs = {\"importer_pk\": \"importer__pk\"}\n    serializer_class = ImportSerializer\n    queryset = Import.objects.all()\n\n\nclass PulpImporterViewSet(ImporterViewSet):\n    \"\"\"ViewSet for PulpImporters.\"\"\"\n\n    endpoint_name = \"pulp\"\n    serializer_class = PulpImporterSerializer\n    queryset = PulpImporter.objects.all()\n\n\nclass PulpImportViewSet(ImportViewSet):\n    \"\"\"ViewSet for PulpImports.\"\"\"\n\n    parent_viewset = PulpImporterViewSet\n    queryset = PulpImport.objects.all()\n\n    @extend_schema(\n        request=PulpImportSerializer,\n        description=\"Trigger an asynchronous task to import a Pulp export.\",\n        responses={202: TaskGroupOperationResponseSerializer},\n    )\n    def create(self, request, importer_pk):\n        \"\"\"Import a Pulp export into Pulp.\"\"\"\n        try:\n            importer = PulpImporter.objects.get(pk=importer_pk)\n        except PulpImporter.DoesNotExist:\n            raise Http404\n\n        serializer = PulpImportSerializer(data=request.data, context={\"request\": request})\n        serializer.is_valid(raise_exception=True)\n\n        path = serializer.validated_data.get(\"path\")\n        toc = serializer.validated_data.get(\"toc\")\n        task_group = TaskGroup.objects.create(description=f\"Import of {path}\")\n\n        dispatch(\n            pulp_import,\n            exclusive_resources=[importer],\n            task_group=task_group,\n            kwargs={\"importer_pk\": importer.pk, \"path\": path, \"toc\": toc},\n        )\n        return TaskGroupOperationResponse(task_group, request)\n", "path": "pulpcore/app/viewsets/importer.py"}, {"content": "from import_export import fields\nfrom import_export.widgets import ForeignKeyWidget\nfrom logging import getLogger\n\nfrom pulpcore.app.models.content import (\n    Artifact,\n    Content,\n    ContentArtifact,\n)\nfrom pulpcore.app.models.repository import Repository\nfrom pulpcore.constants import ALL_KNOWN_CONTENT_CHECKSUMS\nfrom pulpcore.plugin.importexport import QueryModelResource\n\n\nlog = getLogger(__name__)\n\n\n#\n# Artifact and Repository are different from other import-export entities, in that they are not\n# repo-version-specific.\n#\nclass ArtifactResource(QueryModelResource):\n    \"\"\"Resource for import/export of artifacts.\"\"\"\n\n    def before_import_row(self, row, **kwargs):\n        \"\"\"\n        Sets digests to None if they are blank strings.\n\n        Args:\n            row (tablib.Dataset row): incoming import-row representing a single Variant.\n            kwargs: args passed along from the import() call.\n\n        \"\"\"\n        # the export converts None to blank strings but sha384 and sha512 have unique constraints\n        # that get triggered if they are blank. convert checksums back into None if they are blank.\n        for checksum in ALL_KNOWN_CONTENT_CHECKSUMS:\n            if row[checksum] == \"\":\n                row[checksum] = None\n\n    class Meta:\n        model = Artifact\n        exclude = (\n            \"pulp_id\",\n            \"pulp_created\",\n            \"pulp_last_updated\",\n        )\n        import_id_fields = (\"sha256\",)\n\n\nclass RepositoryResource(QueryModelResource):\n    class Meta:\n        model = Repository\n        import_id_fields = (\"name\",)\n        exclude = (\n            \"pulp_id\",\n            \"pulp_created\",\n            \"pulp_last_updated\",\n            \"content\",\n        )\n\n\nclass ContentArtifactResource(QueryModelResource):\n    \"\"\"\n    Handles import/export of the ContentArtifact model.\n\n    ContentArtifact is different from other import-export entities because it has no 'natural key'\n    other than a pulp_id, which aren't shared across instances. We do some magic to link up\n    ContentArtifacts to their matching (already-imported) Content.\n\n    Some plugin-models have sub-repositories. We take advantage of the content-mapping\n    machinery to account for those contentartifacts as well.\n    \"\"\"\n\n    artifact = fields.Field(\n        column_name=\"artifact\", attribute=\"artifact\", widget=ForeignKeyWidget(Artifact, \"sha256\")\n    )\n\n    def __init__(self, repo_version=None, content_mapping=None):\n        self.content_mapping = content_mapping\n        super().__init__(repo_version)\n\n    def before_import_row(self, row, **kwargs):\n        \"\"\"\n        Fixes the content-ptr of an incoming content-artifact row at import time.\n\n        Finds the 'original uuid' of the Content for this row, looks it up as the\n        'upstream_id' of imported Content, and then replaces the Content-pk with its\n        (new) uuid.\n\n        Args:\n            row (tablib.Dataset row): incoming import-row representing a single ContentArtifact.\n            kwargs: args passed along from the import() call.\n\n        Returns:\n            (tablib.Dataset row): row that now points to the new downstream uuid for its content.\n        \"\"\"\n\n        linked_content = Content.objects.get(upstream_id=row[\"content\"])\n        row[\"content\"] = str(linked_content.pulp_id)\n\n    def set_up_queryset(self):\n        vers_content = ContentArtifact.objects.filter(content__in=self.repo_version.content)\n        if self.content_mapping:\n            all_content = []\n            for content_ids in self.content_mapping.values():\n                all_content.extend(content_ids)\n            vers_content = vers_content.union(\n                ContentArtifact.objects.filter(content__in=all_content)\n            )\n        return vers_content.order_by(\"content\", \"relative_path\")\n\n    class Meta:\n        model = ContentArtifact\n        import_id_fields = (\n            \"content\",\n            \"relative_path\",\n        )\n        exclude = (\n            \"pulp_created\",\n            \"pulp_last_updated\",\n            \"_artifacts\",\n            \"pulp_id\",\n        )\n", "path": "pulpcore/app/modelresource.py"}, {"content": "from import_export import resources\n\n\nclass QueryModelResource(resources.ModelResource):\n    \"\"\"\n    A ModelResource that knows the RepositoryVersion to use to filter its query\n\n    QueryModelResource has-a repository-version that can be used to limit its export, and a\n    queryset that is derived from that repository-version.\n\n    A plugin-writer will subclass their ModelResources from QueryModelResource,\n    and use it to define the limiting query\n\n    Attributes:\n\n        repo_version (models.RepositoryVersion): The RepositoryVersion whose content we would like\n            to export\n        queryset (django.db.models.query.QuerySet): filtering queryset for this resource\n            (driven by repo_version)\n    \"\"\"\n\n    def set_up_queryset(self):\n        return None\n\n    def __init__(self, repo_version=None):\n        self.repo_version = repo_version\n        if repo_version:\n            self.queryset = self.set_up_queryset()\n\n    class Meta:\n        exclude = (\"pulp_id\", \"pulp_created\", \"pulp_last_updated\")\n\n\nclass BaseContentResource(QueryModelResource):\n    \"\"\"\n    A QueryModelResource that knows how to fill in the 'upstream_id' export-field\n\n    BaseContentResource knows to de/hydrate upstream_id with the content-being-exported's pulp_id.\n\n    All Content-based resources being import/exported should subclass from this class.\n    \"\"\"\n\n    # An optional mapping that maps Content to Repositories. Useful when Content is sometimes not\n    # tied directly to a Repository but rather to a subrepo. Formatting:\n    #\n    #     {\"<repo name>\": [\"<content upstream_id>\", \"...\"]}\n    #\n    content_mapping = None\n\n    class Meta:\n        exclude = QueryModelResource.Meta.exclude + (\"_artifacts\", \"content\", \"content_ptr\")\n\n    def dehydrate_upstream_id(self, content):\n        return str(content.pulp_id)\n", "path": "pulpcore/plugin/importexport.py"}], "after_files": [{"content": "from django.http import Http404\nfrom django_filters.rest_framework import filters\nfrom drf_spectacular.utils import extend_schema\nfrom rest_framework import mixins\n\nfrom pulpcore.app.models import (\n    Import,\n    Importer,\n    PulpImport,\n    PulpImporter,\n    TaskGroup,\n)\nfrom pulpcore.app.response import TaskGroupOperationResponse\nfrom pulpcore.app.serializers import (\n    ImportSerializer,\n    ImporterSerializer,\n    PulpImporterSerializer,\n    PulpImportSerializer,\n    TaskGroupOperationResponseSerializer,\n)\nfrom pulpcore.app.tasks import pulp_import\nfrom pulpcore.app.viewsets import (\n    BaseFilterSet,\n    NamedModelViewSet,\n)\nfrom pulpcore.app.viewsets.base import NAME_FILTER_OPTIONS\nfrom pulpcore.tasking.tasks import dispatch\n\n\nclass ImporterFilter(BaseFilterSet):\n    \"\"\"Filter for Importers.\"\"\"\n\n    name = filters.CharFilter()\n\n    class Meta:\n        model = Importer\n        fields = {\n            \"name\": NAME_FILTER_OPTIONS,\n        }\n\n\nclass ImporterViewSet(\n    NamedModelViewSet,\n    mixins.CreateModelMixin,\n    mixins.UpdateModelMixin,\n    mixins.RetrieveModelMixin,\n    mixins.ListModelMixin,\n    mixins.DestroyModelMixin,\n):\n    \"\"\"ViewSet for Importers.\"\"\"\n\n    queryset = Importer.objects.all()\n    serializer_class = ImporterSerializer\n    endpoint_name = \"importers\"\n    router_lookup = \"importer\"\n    filterset_class = ImporterFilter\n\n\nclass ImportViewSet(\n    NamedModelViewSet,\n    mixins.CreateModelMixin,\n    mixins.RetrieveModelMixin,\n    mixins.ListModelMixin,\n    mixins.DestroyModelMixin,\n):\n    \"\"\"ViewSet for viewing imports from an Importer.\"\"\"\n\n    endpoint_name = \"imports\"\n    nest_prefix = \"importers\"\n    router_lookup = \"import\"\n    lookup_field = \"pk\"\n    parent_viewset = ImporterViewSet\n    parent_lookup_kwargs = {\"importer_pk\": \"importer__pk\"}\n    serializer_class = ImportSerializer\n    queryset = Import.objects.all()\n\n\nclass PulpImporterViewSet(ImporterViewSet):\n    \"\"\"ViewSet for PulpImporters.\"\"\"\n\n    endpoint_name = \"pulp\"\n    serializer_class = PulpImporterSerializer\n    queryset = PulpImporter.objects.all()\n\n\nclass PulpImportViewSet(ImportViewSet):\n    \"\"\"ViewSet for PulpImports.\"\"\"\n\n    parent_viewset = PulpImporterViewSet\n    queryset = PulpImport.objects.all()\n\n    @extend_schema(\n        request=PulpImportSerializer,\n        description=\"Trigger an asynchronous task to import a Pulp export.\",\n        responses={202: TaskGroupOperationResponseSerializer},\n    )\n    def create(self, request, importer_pk):\n        \"\"\"Import a Pulp export into Pulp.\"\"\"\n        try:\n            importer = PulpImporter.objects.get(pk=importer_pk)\n        except PulpImporter.DoesNotExist:\n            raise Http404\n\n        serializer = PulpImportSerializer(data=request.data, context={\"request\": request})\n        serializer.is_valid(raise_exception=True)\n\n        path = serializer.validated_data.get(\"path\")\n        toc = serializer.validated_data.get(\"toc\")\n        task_group = TaskGroup.objects.create(\n            description=\"Import of {path}\".format(path=path or toc)\n        )\n\n        dispatch(\n            pulp_import,\n            exclusive_resources=[importer],\n            task_group=task_group,\n            kwargs={\"importer_pk\": importer.pk, \"path\": path, \"toc\": toc},\n        )\n        return TaskGroupOperationResponse(task_group, request)\n", "path": "pulpcore/app/viewsets/importer.py"}, {"content": "from import_export import fields\nfrom import_export.widgets import ForeignKeyWidget\nfrom logging import getLogger\n\nfrom pulpcore.app.models.content import (\n    Artifact,\n    Content,\n    ContentArtifact,\n)\nfrom pulpcore.app.models.repository import Repository\nfrom pulpcore.constants import ALL_KNOWN_CONTENT_CHECKSUMS\nfrom pulpcore.plugin.importexport import QueryModelResource\n\n\nlog = getLogger(__name__)\n\n\n#\n# Artifact and Repository are different from other import-export entities, in that they are not\n# repo-version-specific.\n#\nclass ArtifactResource(QueryModelResource):\n    \"\"\"Resource for import/export of artifacts.\"\"\"\n\n    def before_import_row(self, row, **kwargs):\n        \"\"\"\n        Sets digests to None if they are blank strings.\n\n        Args:\n            row (tablib.Dataset row): incoming import-row representing a single Variant.\n            kwargs: args passed along from the import() call.\n\n        \"\"\"\n        # the export converts None to blank strings but sha384 and sha512 have unique constraints\n        # that get triggered if they are blank. convert checksums back into None if they are blank.\n        for checksum in ALL_KNOWN_CONTENT_CHECKSUMS:\n            if row[checksum] == \"\":\n                row[checksum] = None\n\n    class Meta:\n        model = Artifact\n        exclude = (\n            \"pulp_id\",\n            \"pulp_created\",\n            \"pulp_last_updated\",\n            \"timestamp_of_interest\",\n        )\n        import_id_fields = (\"sha256\",)\n\n\nclass RepositoryResource(QueryModelResource):\n    class Meta:\n        model = Repository\n        import_id_fields = (\"name\",)\n        exclude = (\n            \"pulp_id\",\n            \"pulp_created\",\n            \"pulp_last_updated\",\n            \"content\",\n        )\n\n\nclass ContentArtifactResource(QueryModelResource):\n    \"\"\"\n    Handles import/export of the ContentArtifact model.\n\n    ContentArtifact is different from other import-export entities because it has no 'natural key'\n    other than a pulp_id, which aren't shared across instances. We do some magic to link up\n    ContentArtifacts to their matching (already-imported) Content.\n\n    Some plugin-models have sub-repositories. We take advantage of the content-mapping\n    machinery to account for those contentartifacts as well.\n    \"\"\"\n\n    artifact = fields.Field(\n        column_name=\"artifact\", attribute=\"artifact\", widget=ForeignKeyWidget(Artifact, \"sha256\")\n    )\n\n    def __init__(self, repo_version=None, content_mapping=None):\n        self.content_mapping = content_mapping\n        super().__init__(repo_version)\n\n    def before_import_row(self, row, **kwargs):\n        \"\"\"\n        Fixes the content-ptr of an incoming content-artifact row at import time.\n\n        Finds the 'original uuid' of the Content for this row, looks it up as the\n        'upstream_id' of imported Content, and then replaces the Content-pk with its\n        (new) uuid.\n\n        Args:\n            row (tablib.Dataset row): incoming import-row representing a single ContentArtifact.\n            kwargs: args passed along from the import() call.\n\n        Returns:\n            (tablib.Dataset row): row that now points to the new downstream uuid for its content.\n        \"\"\"\n\n        linked_content = Content.objects.get(upstream_id=row[\"content\"])\n        row[\"content\"] = str(linked_content.pulp_id)\n\n    def set_up_queryset(self):\n        content_pks = set(self.repo_version.content.values_list(\"pk\", flat=True))\n\n        if self.content_mapping:\n            for content_ids in self.content_mapping.values():\n                content_pks |= set(content_ids)\n\n        return (\n            ContentArtifact.objects.filter(content__in=content_pks)\n            .order_by(\"content\", \"relative_path\")\n            .select_related(\"artifact\")\n        )\n\n    def dehydrate_content(self, content_artifact):\n        return str(content_artifact.content_id)\n\n    class Meta:\n        model = ContentArtifact\n        import_id_fields = (\n            \"content\",\n            \"relative_path\",\n        )\n        exclude = (\n            \"pulp_created\",\n            \"pulp_last_updated\",\n            \"_artifacts\",\n            \"pulp_id\",\n        )\n", "path": "pulpcore/app/modelresource.py"}, {"content": "from import_export import resources\n\n\nclass QueryModelResource(resources.ModelResource):\n    \"\"\"\n    A ModelResource that knows the RepositoryVersion to use to filter its query\n\n    QueryModelResource has-a repository-version that can be used to limit its export, and a\n    queryset that is derived from that repository-version.\n\n    A plugin-writer will subclass their ModelResources from QueryModelResource,\n    and use it to define the limiting query\n\n    Attributes:\n\n        repo_version (models.RepositoryVersion): The RepositoryVersion whose content we would like\n            to export\n        queryset (django.db.models.query.QuerySet): filtering queryset for this resource\n            (driven by repo_version)\n    \"\"\"\n\n    def set_up_queryset(self):\n        return None\n\n    def dehydrate_pulp_domain(self, content):\n        return str(content.pulp_domain_id)\n\n    def __init__(self, repo_version=None):\n        self.repo_version = repo_version\n        if repo_version:\n            self.queryset = self.set_up_queryset()\n\n    class Meta:\n        exclude = (\"pulp_id\", \"pulp_created\", \"pulp_last_updated\")\n\n\nclass BaseContentResource(QueryModelResource):\n    \"\"\"\n    A QueryModelResource that knows how to fill in the 'upstream_id' export-field\n\n    BaseContentResource knows to de/hydrate upstream_id with the content-being-exported's pulp_id.\n\n    All Content-based resources being import/exported should subclass from this class.\n    \"\"\"\n\n    # An optional mapping that maps Content to Repositories. Useful when Content is sometimes not\n    # tied directly to a Repository but rather to a subrepo. Formatting:\n    #\n    #     {\"<repo name>\": [\"<content upstream_id>\", \"...\"]}\n    #\n    content_mapping = None\n\n    def dehydrate_upstream_id(self, content):\n        return str(content.pulp_id)\n\n    class Meta:\n        exclude = QueryModelResource.Meta.exclude + (\n            \"_artifacts\",\n            \"content\",\n            \"content_ptr\",\n            \"timestamp_of_interest\",\n        )\n", "path": "pulpcore/plugin/importexport.py"}]}