4949
5050from qfieldcloud .core import exceptions
5151from qfieldcloud .core .models import (
52+ SHARED_DATASETS_PROJECT_NAME ,
5253 ApplyJob ,
5354 ApplyJobDelta ,
5455 Delta ,
7172from qfieldcloud .core .utils2 import delta_utils , jobs , pg_service_file
7273from qfieldcloud .filestorage .backend import QfcS3Boto3Storage
7374from qfieldcloud .filestorage .models import File
75+ from qfieldcloud .subscription .models import get_subscription_model
7476
7577
7678class QfcAdminSite (AdminSite ):
@@ -147,8 +149,37 @@ class ModelAdminEstimateCountMixin:
147149 list_per_page = settings .QFIELDCLOUD_ADMIN_LIST_PER_PAGE
148150
149151
152+ class ModelAdminSearchParserMixin :
153+ """
154+ Mixin to add search parser to the model admin.
155+ """
156+
157+ search_parser_config : dict [str , dict [str , Any ]] | None = None
158+
159+ def get_search_results (
160+ self , request : HttpRequest , queryset : QuerySet , search_term : str
161+ ) -> tuple [QuerySet , bool ]:
162+ if self .search_parser_config :
163+ filters = search_parser (
164+ request ,
165+ queryset ,
166+ search_term ,
167+ self .search_parser_config ,
168+ )
169+
170+ if filters :
171+ # Bypass standard search to avoid literal matching,
172+ # Return True to enable distinct to handle potential duplicates.
173+ return queryset .filter (** filters ), True
174+
175+ return super ().get_search_results (request , queryset , search_term ) # type: ignore
176+
177+
150178class QFieldCloudModelAdmin ( # type: ignore
151- ModelAdminNoPkOrderChangeListMixin , ModelAdminEstimateCountMixin , admin .ModelAdmin
179+ ModelAdminNoPkOrderChangeListMixin ,
180+ ModelAdminEstimateCountMixin ,
181+ ModelAdminSearchParserMixin ,
182+ admin .ModelAdmin ,
152183):
153184 def has_delete_permission (self , request , obj = None ):
154185 """Reimplementing this Django Admin method to allow deleting related objects in django admin from another ModelAdmin.
@@ -565,6 +596,26 @@ def save_model(self, request, obj, form, change):
565596 obj .clean ()
566597 obj .save ()
567598
599+ def change_view (
600+ self ,
601+ request : HttpRequest ,
602+ object_id : str ,
603+ form_url : str = "" ,
604+ extra_context : Any | None = None ,
605+ ) -> HttpResponse :
606+ # Add the subscription model is editable flag to the extra context
607+ extra_context = extra_context or {}
608+
609+ extra_context .update (
610+ {
611+ "subscription_model" : get_subscription_model (),
612+ }
613+ )
614+
615+ return super ().change_view (
616+ request , object_id , form_url , extra_context = extra_context
617+ )
618+
568619 def get_urls (self ):
569620 urls = super ().get_urls ()
570621
@@ -884,6 +935,26 @@ def clean_are_attachments_versioned(self):
884935
885936 return value
886937
938+ def clean (self ):
939+ cleaned_data = super ().clean ()
940+ name = cleaned_data .get ("name" )
941+
942+ if name and name .lower () == SHARED_DATASETS_PROJECT_NAME :
943+ if (
944+ self .instance .pk
945+ and self .instance .name .lower () == SHARED_DATASETS_PROJECT_NAME
946+ ):
947+ pass
948+
949+ elif self .instance .has_the_qgis_file :
950+ raise ValidationError (
951+ _ (
952+ "Cannot rename project to '{}' because it contains a QGIS project file."
953+ ).format (name )
954+ )
955+
956+ return cleaned_data
957+
887958
888959class ProjectAdmin (QFieldCloudModelAdmin ):
889960 form = ProjectForm
@@ -960,6 +1031,18 @@ class ProjectAdmin(QFieldCloudModelAdmin):
9601031
9611032 change_form_template = "admin/project_change_form.html"
9621033
1034+ search_parser_config = {
1035+ "owner" : {
1036+ "filter" : "owner__username__iexact" ,
1037+ },
1038+ "collaborator" : {
1039+ "filter" : "user_roles__user__username__iexact" ,
1040+ "extra_filters" : {
1041+ "is_public" : False ,
1042+ },
1043+ },
1044+ }
1045+
9631046 def get_form (self , * args , ** kwargs ):
9641047 help_texts = {
9651048 "file_storage_bytes" : _ (
@@ -969,33 +1052,6 @@ def get_form(self, *args, **kwargs):
9691052 kwargs .update ({"help_texts" : help_texts })
9701053 return super ().get_form (* args , ** kwargs )
9711054
972- def get_search_results (self , request , queryset , search_term ):
973- filters = search_parser (
974- request ,
975- queryset ,
976- search_term ,
977- {
978- "owner" : {
979- "filter" : "owner__username__iexact" ,
980- },
981- "collaborator" : {
982- "filter" : "user_roles__user__username__iexact" ,
983- "extra_filters" : {
984- "is_public" : False ,
985- },
986- },
987- },
988- )
989-
990- if filters :
991- return queryset .filter (** filters ), True
992-
993- queryset , use_distinct = super ().get_search_results (
994- request , queryset , search_term
995- )
996-
997- return queryset , use_distinct
998-
9991055 def project_files (self , instance ):
10001056 return instance .pk
10011057
@@ -1107,6 +1163,12 @@ class JobAdmin(QFieldCloudModelAdmin):
11071163
11081164 change_form_template = "admin/job_change_form.html"
11091165
1166+ search_parser_config = {
1167+ "created_by" : {
1168+ "filter" : "created_by__username__iexact" ,
1169+ },
1170+ }
1171+
11101172 def get_queryset (self , request ):
11111173 return super ().get_queryset (request ).defer ("output" , "feedback" )
11121174
@@ -1325,6 +1387,12 @@ class DeltaAdmin(QFieldCloudModelAdmin):
13251387
13261388 change_form_template = "admin/delta_change_form.html"
13271389
1390+ search_parser_config = {
1391+ "created_by" : {
1392+ "filter" : "created_by__username__iexact" ,
1393+ },
1394+ }
1395+
13281396 def old_geom_truncated (self , instance ):
13291397 return self .geom_truncated (instance .old_geom )
13301398
@@ -1333,7 +1401,10 @@ def new_geom_truncated(self, instance):
13331401
13341402 # Show geometries only truncated as they are fully shown in content
13351403 def geom_truncated (self , geom ):
1336- return f"{ str (geom )[:70 ]} ..." if geom else "-"
1404+ if geom :
1405+ return f"{ str (geom )[:70 ]} ..."
1406+ else :
1407+ return "-"
13371408
13381409 # This will disable add functionality
13391410
@@ -1487,6 +1558,15 @@ class OrganizationAdmin(QFieldCloudModelAdmin):
14871558
14881559 autocomplete_fields = ("organization_owner" ,)
14891560
1561+ search_parser_config = {
1562+ "owner" : {
1563+ "filter" : "organization_owner__username__iexact" ,
1564+ },
1565+ "member" : {
1566+ "filter" : "membership_roles__user__username__iexact" ,
1567+ },
1568+ }
1569+
14901570 @admin .display (description = _ ("Active members" ))
14911571 def active_users_links (self , instance ) -> str :
14921572 persons = instance .useraccount .current_subscription .active_users
@@ -1516,30 +1596,6 @@ def storage_usage__field(self, instance) -> str:
15161596 used_storage_perc = instance .useraccount .storage_used_ratio * 100
15171597 return f"{ used_storage } { free_storage } ({ used_storage_perc :.2f} %)"
15181598
1519- def get_search_results (self , request , queryset , search_term ):
1520- filters = search_parser (
1521- request ,
1522- queryset ,
1523- search_term ,
1524- {
1525- "owner" : {
1526- "filter" : "organization_owner__username__iexact" ,
1527- },
1528- "member" : {
1529- "filter" : "membership_roles__user__username__iexact" ,
1530- },
1531- },
1532- )
1533-
1534- if filters :
1535- return queryset .filter (** filters ), True
1536-
1537- queryset , use_distinct = super ().get_search_results (
1538- request , queryset , search_term
1539- )
1540-
1541- return queryset , use_distinct
1542-
15431599 def save_formset (self , request , form , formset , change ):
15441600 for form_obj in formset :
15451601 if isinstance (form_obj .instance , OrganizationMember ):
@@ -1644,10 +1700,19 @@ def lookups(self, request, model_admin):
16441700
16451701
16461702class LogEntryAdmin (
1647- ModelAdminNoPkOrderChangeListMixin , ModelAdminEstimateCountMixin , BaseLogEntryAdmin
1703+ ModelAdminNoPkOrderChangeListMixin ,
1704+ ModelAdminEstimateCountMixin ,
1705+ ModelAdminSearchParserMixin ,
1706+ BaseLogEntryAdmin ,
16481707):
16491708 list_filter = ("action" , QFieldCloudResourceTypeFilter )
16501709
1710+ search_parser_config = {
1711+ "user" : {
1712+ "filter" : "actor__username__iexact" ,
1713+ },
1714+ }
1715+
16511716
16521717class FaultyDeltaFilesAdmin (QFieldCloudModelAdmin ):
16531718 list_display = (
@@ -1681,6 +1746,12 @@ class FaultyDeltaFilesAdmin(QFieldCloudModelAdmin):
16811746
16821747 exclude = ("traceback" ,)
16831748
1749+ search_parser_config = {
1750+ "owner" : {
1751+ "filter" : "project__owner__username__iexact" ,
1752+ },
1753+ }
1754+
16841755 def traceback__pre (self , instance ) -> str :
16851756 return format_pre (instance .traceback )
16861757
0 commit comments