|
6 | 6 |
|
7 | 7 | from django.conf import settings |
8 | 8 | from django.contrib.sites.models import Site |
| 9 | +from django.db.models import Exists |
| 10 | +from django.db.models import F |
9 | 11 | from django.db.models import OuterRef |
10 | | -from django.db.models import Q |
11 | 12 | from django.db.models import Subquery |
| 13 | +from django.db.models.sql.constants import LOUTER |
12 | 14 | from django.utils.translation import gettext as _ |
13 | 15 | from le_utils.constants import content_kinds |
14 | 16 |
|
| 17 | +from contentcuration.db.models.query import With |
15 | 18 | from contentcuration.models import Channel |
| 19 | +from contentcuration.models import ContentNode |
16 | 20 | from contentcuration.models import generate_storage_url |
17 | 21 |
|
18 | 22 | if not os.path.exists(settings.CSV_ROOT): |
@@ -43,29 +47,24 @@ def generate_user_csv_filename(user): |
43 | 47 |
|
44 | 48 |
|
45 | 49 | def _write_user_row(file, writer, domain): |
46 | | - filename = "{}.{}".format(file["checksum"], file["file_format__extension"]) |
| 50 | + filename = "{}.{}".format(file["checksum"], file["file_extension"]) |
47 | 51 | writer.writerow( |
48 | 52 | [ |
49 | 53 | file["channel_name"] or _("No Channel"), |
50 | | - file["contentnode__title"] or _("No resource"), |
| 54 | + file["node_title"] or _("No resource"), |
51 | 55 | next( |
52 | | - ( |
53 | | - k[1] |
54 | | - for k in content_kinds.choices |
55 | | - if k[0] == file["contentnode__kind_id"] |
56 | | - ), |
| 56 | + (k[1] for k in content_kinds.choices if k[0] == file["node_kind_id"]), |
57 | 57 | "", |
58 | 58 | ), |
59 | 59 | file["original_filename"], |
60 | 60 | _format_size(file["file_size"] or 0), |
61 | 61 | generate_storage_url(filename), |
62 | | - file["contentnode__description"], |
63 | | - file["contentnode__author"], |
64 | | - file["language__readable_name"] |
65 | | - or file["contentnode__language__readable_name"], |
66 | | - file["contentnode__license__license_name"], |
67 | | - file["contentnode__license_description"], |
68 | | - file["contentnode__copyright_holder"], |
| 62 | + file["node_description"], |
| 63 | + file["node_author"], |
| 64 | + file["file_language"] or file["node_language"], |
| 65 | + file["node_license_name"], |
| 66 | + file["node_license_description"], |
| 67 | + file["node_copyright_holder"], |
69 | 68 | ] |
70 | 69 | ) |
71 | 70 |
|
@@ -100,34 +99,105 @@ def write_user_csv(user, path=None): |
100 | 99 |
|
101 | 100 | domain = Site.objects.get(pk=1).domain |
102 | 101 |
|
103 | | - # Get all user files |
104 | | - channel_query = Channel.objects.filter( |
105 | | - Q(main_tree__tree_id=OuterRef("contentnode__tree_id")) |
106 | | - | Q(trash_tree__tree_id=OuterRef("contentnode__tree_id")) |
| 102 | + # Build CTEs so we first reduce to this user's files, then resolve only |
| 103 | + # needed content node and channel fields. |
| 104 | + user_files_cte = With( |
| 105 | + user.files.values( |
| 106 | + "id", |
| 107 | + "contentnode_id", |
| 108 | + "original_filename", |
| 109 | + "file_size", |
| 110 | + "checksum", |
| 111 | + file_extension=F("file_format__extension"), |
| 112 | + file_language=F("language__readable_name"), |
| 113 | + ), |
| 114 | + name="user_files", |
| 115 | + ) |
| 116 | + |
| 117 | + content_nodes_cte = With( |
| 118 | + user_files_cte.join( |
| 119 | + ContentNode.objects.all(), |
| 120 | + id=user_files_cte.col.contentnode_id, |
| 121 | + ) |
| 122 | + .values( |
| 123 | + "id", |
| 124 | + "tree_id", |
| 125 | + node_title=F("title"), |
| 126 | + node_kind_id=F("kind_id"), |
| 127 | + node_description=F("description"), |
| 128 | + node_author=F("author"), |
| 129 | + node_language=F("language__readable_name"), |
| 130 | + node_license_name=F("license__license_name"), |
| 131 | + node_license_description=F("license_description"), |
| 132 | + node_copyright_holder=F("copyright_holder"), |
| 133 | + ) |
| 134 | + .distinct(), |
| 135 | + name="content_nodes", |
| 136 | + ) |
| 137 | + |
| 138 | + main_channel_names = Channel.objects.filter( |
| 139 | + Exists( |
| 140 | + content_nodes_cte.queryset().filter( |
| 141 | + tree_id=OuterRef("main_tree__tree_id") |
| 142 | + ) |
| 143 | + ) |
| 144 | + ).values( |
| 145 | + tree_id=F("main_tree__tree_id"), |
| 146 | + channel_name=F("name"), |
| 147 | + ) |
| 148 | + trash_channel_names = Channel.objects.filter( |
| 149 | + Exists( |
| 150 | + content_nodes_cte.queryset().filter( |
| 151 | + tree_id=OuterRef("trash_tree__tree_id") |
| 152 | + ) |
| 153 | + ) |
| 154 | + ).values( |
| 155 | + tree_id=F("trash_tree__tree_id"), |
| 156 | + channel_name=F("name"), |
| 157 | + ) |
| 158 | + channel_names_cte = With( |
| 159 | + main_channel_names.union(trash_channel_names), name="channel_names" |
107 | 160 | ) |
108 | 161 |
|
109 | 162 | user_files = ( |
110 | | - user.files.select_related("language", "contentnode", "file_format") |
| 163 | + content_nodes_cte.join( |
| 164 | + user_files_cte.queryset(), |
| 165 | + contentnode_id=content_nodes_cte.col.id, |
| 166 | + _join_type=LOUTER, |
| 167 | + ) |
| 168 | + .with_cte(user_files_cte) |
| 169 | + .with_cte(content_nodes_cte) |
| 170 | + .with_cte(channel_names_cte) |
111 | 171 | .annotate( |
112 | | - channel_name=Subquery(channel_query.values_list("name", flat=True)[:1]) |
| 172 | + channel_name=Subquery( |
| 173 | + channel_names_cte.queryset() |
| 174 | + .filter(tree_id=content_nodes_cte.col.tree_id) |
| 175 | + .values("channel_name")[:1] |
| 176 | + ), |
| 177 | + node_title=content_nodes_cte.col.node_title, |
| 178 | + node_kind_id=content_nodes_cte.col.node_kind_id, |
| 179 | + node_description=content_nodes_cte.col.node_description, |
| 180 | + node_author=content_nodes_cte.col.node_author, |
| 181 | + node_language=content_nodes_cte.col.node_language, |
| 182 | + node_license_name=content_nodes_cte.col.node_license_name, |
| 183 | + node_license_description=content_nodes_cte.col.node_license_description, |
| 184 | + node_copyright_holder=content_nodes_cte.col.node_copyright_holder, |
113 | 185 | ) |
114 | 186 | .values( |
115 | 187 | "channel_name", |
116 | 188 | "original_filename", |
117 | 189 | "file_size", |
118 | 190 | "checksum", |
119 | | - "file_format__extension", |
120 | | - "language__readable_name", |
121 | | - "contentnode__title", |
122 | | - "contentnode__language__readable_name", |
123 | | - "contentnode__license__license_name", |
124 | | - "contentnode__kind_id", |
125 | | - "contentnode__description", |
126 | | - "contentnode__author", |
127 | | - "contentnode__provider", |
128 | | - "contentnode__aggregator", |
129 | | - "contentnode__license_description", |
130 | | - "contentnode__copyright_holder", |
| 191 | + "file_extension", |
| 192 | + "file_language", |
| 193 | + "node_title", |
| 194 | + "node_kind_id", |
| 195 | + "node_description", |
| 196 | + "node_author", |
| 197 | + "node_language", |
| 198 | + "node_license_name", |
| 199 | + "node_license_description", |
| 200 | + "node_copyright_holder", |
131 | 201 | ) |
132 | 202 | ) |
133 | 203 | for file in user_files: |
|
0 commit comments