66from django .contrib .contenttypes .forms import generic_inlineformset_factory
77from django .core .cache import cache
88from django .db import connection
9- from django .test import TestCase
9+ from django .test import TestCase , override_settings
1010from django .urls import reverse
1111from django .utils .timezone import datetime , now , timedelta
1212from freezegun import freeze_time
@@ -89,6 +89,28 @@ def _login_admin(self):
8989 u = User .objects .create_superuser ('admin' , 'admin' , 'test@test.com' )
9090 self .client .force_login (u )
9191
92+ def _get_inline_admin_heading (self , heading ):
93+ """
94+ TODO: Remove this when dropping support for Django 4.2
95+
96+ Django 5.1 introduced a new way to render inline headings with IDs and classes.
97+ For versions before 5.1, we use the old-style heading format except for "Alert Settings"
98+ which is already handled differently by django-nested-admin==4.1.0.
99+ This method helps us generate the appropriate HTML heading format
100+ based on the Django version being used.
101+ """
102+ if django .VERSION < (5 , 1 ) and heading != 'Alert Settings' :
103+ return f'<h2>{ heading } </h2>'
104+ heading_map = {
105+ 'Checks' : f'{ Check ._meta .app_label } -check-content_type-object_id-heading' ,
106+ 'Alert Settings' : f'{ Metric ._meta .app_label } -metric-content_type-object_id-heading' ,
107+ 'WiFi Sessions' : 'wifisession_set-heading' ,
108+ 'Configuration' : 'config-heading' ,
109+ 'Map' : 'devicelocation-heading' ,
110+ 'Credentials' : 'deviceconnection_set-heading' ,
111+ }
112+ return f'<h2 id="{ heading_map [heading ]} " class="inline-heading">\n \n { heading } \n \n </h2>'
113+
92114 def test_device_admin (self ):
93115 dd = self .create_test_data ()
94116 check = Check .objects .create (
@@ -101,7 +123,9 @@ def test_device_admin(self):
101123 response = self .client .get (url )
102124 self .assertContains (response , '<h2>Status</h2>' )
103125 self .assertContains (response , '<h2>Charts</h2>' )
104- self .assertContains (response , '<h2>Checks</h2>' )
126+ self .assertContains (
127+ response , self ._get_inline_admin_heading ('Checks' ), html = True
128+ )
105129 self .assertContains (response , 'Storage' )
106130 self .assertContains (response , 'CPU' )
107131 self .assertContains (response , 'RAM status' )
@@ -315,9 +339,11 @@ def test_device_add_view(self):
315339 url = reverse ('admin:config_device_add' )
316340 r = self .client .get (url )
317341 self .assertNotContains (r , 'AlertSettings' )
318- self .assertContains (r , '<h2>Configuration</h2>' )
319- self .assertContains (r , '<h2>Map</h2>' )
320- self .assertContains (r , '<h2>Credentials</h2>' )
342+ self .assertContains (
343+ r , self ._get_inline_admin_heading ('Configuration' ), html = True
344+ )
345+ self .assertContains (r , self ._get_inline_admin_heading ('Map' ), html = True )
346+ self .assertContains (r , self ._get_inline_admin_heading ('Credentials' ), html = True )
321347
322348 def test_device_disabled_organization_admin (self ):
323349 self .create_test_data ()
@@ -335,8 +361,12 @@ def test_device_disabled_organization_admin(self):
335361 response = self .client .get (url )
336362 self .assertContains (response , '<h2>Status</h2>' )
337363 self .assertContains (response , '<h2>Charts</h2>' )
338- self .assertNotContains (response , '<h2>Checks</h2>' )
339- self .assertNotContains (response , '<h2>AlertSettings</h2>' )
364+ self .assertNotContains (
365+ response , self ._get_inline_admin_heading ('Checks' ), html = True
366+ )
367+ self .assertNotContains (
368+ response , self ._get_inline_admin_heading ('Alert Settings' ), html = True
369+ )
340370
341371 def test_remove_invalid_interface (self ):
342372 d = self ._create_device (organization = self ._create_org ())
@@ -535,22 +565,34 @@ def test_wifisession_inline(self):
535565
536566 with self .subTest ('Test inline absent when no WiFiSession is present' ):
537567 response = self .client .get (path )
538- self .assertNotContains (response , '<h2>WiFi Sessions</h2>' )
568+ self .assertNotContains (
569+ response ,
570+ self ._get_inline_admin_heading ('WiFi Sessions' ),
571+ html = True ,
572+ )
539573 self .assertNotContains (response , 'monitoring-wifisession-changelist-url' )
540574
541575 wifi_session = self ._create_wifi_session (device = device )
542576
543577 with self .subTest ('Test inline present when WiFiSession is open' ):
544578 response = self .client .get (path )
545- self .assertContains (response , '<h2>WiFi Sessions</h2>' )
579+ self .assertContains (
580+ response ,
581+ self ._get_inline_admin_heading ('WiFi Sessions' ),
582+ html = True ,
583+ )
546584 self .assertContains (response , 'monitoring-wifisession-changelist-url' )
547585
548586 wifi_session .stop_time = now ()
549587 wifi_session .save ()
550588
551589 with self .subTest ('Test inline absent when WiFiSession is closed' ):
552590 response = self .client .get (path )
553- self .assertNotContains (response , '<h2>WiFi Sessions</h2>' )
591+ self .assertNotContains (
592+ response ,
593+ self ._get_inline_admin_heading ('WiFi Sessions' ),
594+ html = True ,
595+ )
554596 self .assertNotContains (response , 'monitoring-wifisession-changelist-url' )
555597
556598 def test_check_alertsetting_inline (self ):
@@ -584,13 +626,17 @@ def _add_user_permissions(user, permission_query, expected_perm_count):
584626 self .assertEqual (user .user_permissions .count (), expected_perm_count )
585627
586628 def _assert_check_inline_in_response (response ):
587- self .assertContains (response , '<h2>Checks</h2>' , html = True )
629+ self .assertContains (
630+ response , self ._get_inline_admin_heading ('Checks' ), html = True
631+ )
588632 self .assertContains (response , 'check-content_type-object_id-0-is_active' )
589633 self .assertContains (response , 'check-content_type-object_id-0-check_type' )
590634 self .assertContains (response , 'check-content_type-object_id-0-DELETE' )
591635
592636 def _assert_alertsettings_inline_in_response (response ):
593- self .assertContains (response , '<h2>Alert Settings</h2>' , html = True )
637+ self .assertContains (
638+ response , self ._get_inline_admin_heading ('Alert Settings' ), html = True
639+ )
594640 self .assertContains (response , 'form-row field-name' )
595641 self .assertContains (
596642 response ,
@@ -620,8 +666,12 @@ def _assert_alertsettings_inline_in_response(response):
620666 _add_device_permissions (test_user )
621667 response = self .client .get (url )
622668 self .assertEqual (response .status_code , 200 )
623- self .assertNotContains (response , '<h2>Checks</h2>' , html = True )
624- self .assertNotContains (response , '<h2>Alert Settings</h2>' , html = True )
669+ self .assertNotContains (
670+ response , self ._get_inline_admin_heading ('Checks' ), html = True
671+ )
672+ self .assertNotContains (
673+ response , self ._get_inline_admin_heading ('Alert Settings' ), html = True
674+ )
625675
626676 with self .subTest ('Test check & alert settings with model permissions' ):
627677 _add_device_permissions (test_user )
@@ -659,10 +709,14 @@ def _assert_alertsettings_inline_in_response(response):
659709 )
660710 response = self .client .get (url )
661711 self .assertEqual (response .status_code , 200 )
662- self .assertContains (response , '<h2>Checks</h2>' , html = True )
712+ self .assertContains (
713+ response , self ._get_inline_admin_heading ('Checks' ), html = True
714+ )
663715 self .assertContains (response , 'form-row field-check_type' )
664716 self .assertContains (response , 'form-row field-is_active' )
665- self .assertContains (response , '<h2>Alert Settings</h2>' , html = True )
717+ self .assertContains (
718+ response , self ._get_inline_admin_heading ('Alert Settings' ), html = True
719+ )
666720 self .assertContains (response , 'form-row field-is_healthy djn-form-row-last' )
667721 self .assertContains (
668722 response ,
@@ -1064,44 +1118,43 @@ def test_wifi_client_he_vht_ht_unknown(self):
10641118 """
10651119 <div class="form-row field-he">
10661120 <div>
1067- {start_div}
1121+ <div class="flex-container">
10681122 <label>WiFi 6 (802.11ax):</label>
10691123 <div class="readonly">
10701124 <img src="/static/admin/img/icon-unknown.svg">
10711125 </div>
1072- {end_div}
1126+ </div>
10731127 </div>
10741128 </div>
10751129 <div class="form-row field-vht">
10761130 <div>
1077- {start_div}<label>WiFi 5 (802.11ac):</label>
1131+ <div class="flex-container">
1132+ <label>WiFi 5 (802.11ac):</label>
10781133 <div class="readonly">
10791134 <img src="/static/admin/img/icon-unknown.svg">
10801135 </div>
1081- {end_div}
1136+ </div>
10821137 </div>
10831138 </div>
10841139 <div class="form-row field-ht">
10851140 <div>
1086- {start_div}<label>WiFi 4 (802.11n):</label>
1141+ <div class="flex-container">
1142+ <label>WiFi 4 (802.11n):</label>
10871143 <div class="readonly">
10881144 <img src="/static/admin/img/icon-unknown.svg">
10891145 </div>
1090- {end_div}
1146+ </div>
10911147 </div>
10921148 </div>
1093- """ .format (
1094- # TODO: Remove this when dropping support for Django 3.2 and 4.0
1095- start_div = (
1096- '<div class="flex-container">'
1097- if django .VERSION >= (4 , 2 )
1098- else ''
1099- ),
1100- end_div = '</div>' if django .VERSION >= (4 , 2 ) else '' ,
1101- ),
1149+ """ ,
11021150 html = True ,
11031151 )
11041152
1153+ # TODO: Remove override_setting when dropping support for Django 4.2
1154+ # The DATETIME_FORMAT for en-gb locale changed for Django 5.1+,
1155+ # thus we override the project setting here to have consistent
1156+ # result with tests.
1157+ @override_settings (LANGUAGE_CODE = 'en' )
11051158 def test_wifi_session_stop_time_formatting (self ):
11061159 start_time = datetime .strptime ('2023-8-24 17:08:00' , '%Y-%m-%d %H:%M:%S' )
11071160 stop_time = datetime .strptime ('2023-8-24 19:46:00' , '%Y-%m-%d %H:%M:%S' )
0 commit comments