2010-09-27 23:11:03 +08:00
from django . contrib . admin . helpers import InlineAdminForm
2011-10-07 08:41:25 +08:00
from django . contrib . auth . models import User , Permission
2010-09-27 23:11:03 +08:00
from django . contrib . contenttypes . models import ContentType
2010-12-04 15:28:12 +08:00
from django . test import TestCase
2010-02-24 01:14:50 +08:00
# local test models
2011-09-21 02:16:49 +08:00
from models import ( Holder , Inner , Holder2 , Inner2 , Holder3 ,
2011-09-21 22:00:58 +08:00
Inner3 , Person , OutfitItem , Fashionista , Teacher , Parent , Child ,
2011-10-07 08:41:25 +08:00
Author , Book )
2011-09-21 02:16:49 +08:00
from admin import InnerInline
2010-09-27 23:11:03 +08:00
2010-03-27 07:38:05 +08:00
2010-02-24 01:14:50 +08:00
class TestInline ( TestCase ) :
2011-09-21 02:16:49 +08:00
urls = " regressiontests.admin_inlines.urls "
2010-02-24 01:14:50 +08:00
fixtures = [ ' admin-views-users.xml ' ]
def setUp ( self ) :
holder = Holder ( dummy = 13 )
holder . save ( )
Inner ( dummy = 42 , holder = holder ) . save ( )
2011-09-21 02:16:49 +08:00
self . change_url = ' /admin/admin_inlines/holder/ %i / ' % holder . id
2010-02-24 01:14:50 +08:00
result = self . client . login ( username = ' super ' , password = ' secret ' )
2010-12-04 15:28:12 +08:00
self . assertEqual ( result , True )
2010-02-24 01:14:50 +08:00
def tearDown ( self ) :
self . client . logout ( )
def test_can_delete ( self ) :
"""
can_delete should be passed to inlineformset factory .
"""
response = self . client . get ( self . change_url )
inner_formset = response . context [ - 1 ] [ ' inline_admin_formsets ' ] [ 0 ] . formset
expected = InnerInline . can_delete
actual = inner_formset . can_delete
self . assertEqual ( expected , actual , ' can_delete must be equal ' )
2010-03-27 07:38:05 +08:00
def test_readonly_stacked_inline_label ( self ) :
""" Bug #13174. """
holder = Holder . objects . create ( dummy = 42 )
inner = Inner . objects . create ( holder = holder , dummy = 42 , readonly = ' ' )
2011-09-21 02:16:49 +08:00
response = self . client . get ( ' /admin/admin_inlines/holder/ %i / '
2010-03-27 07:38:05 +08:00
% holder . id )
self . assertContains ( response , ' <label>Inner readonly label:</label> ' )
2010-04-27 20:35:49 +08:00
def test_many_to_many_inlines ( self ) :
" Autogenerated many-to-many inlines are displayed correctly (#13407) "
2011-09-21 02:16:49 +08:00
response = self . client . get ( ' /admin/admin_inlines/author/add/ ' )
2010-04-27 20:35:49 +08:00
# The heading for the m2m inline block uses the right text
self . assertContains ( response , ' <h2>Author-book relationships</h2> ' )
# The "add another" label is correct
self . assertContains ( response , ' Add another Author-Book Relationship ' )
# The '+' is dropped from the autogenerated form prefix (Author_books+)
self . assertContains ( response , ' id= " id_Author_books-TOTAL_FORMS " ' )
2010-04-27 23:05:38 +08:00
def test_inline_primary ( self ) :
person = Person . objects . create ( firstname = ' Imelda ' )
item = OutfitItem . objects . create ( name = ' Shoes ' )
# Imelda likes shoes, but can't cary her own bags.
data = {
' shoppingweakness_set-TOTAL_FORMS ' : 1 ,
' shoppingweakness_set-INITIAL_FORMS ' : 0 ,
' shoppingweakness_set-MAX_NUM_FORMS ' : 0 ,
' _save ' : u ' Save ' ,
' person ' : person . id ,
' max_weight ' : 0 ,
' shoppingweakness_set-0-item ' : item . id ,
}
2011-09-21 02:16:49 +08:00
response = self . client . post ( ' /admin/admin_inlines/fashionista/add/ ' , data )
2010-04-27 23:05:38 +08:00
self . assertEqual ( response . status_code , 302 )
self . assertEqual ( len ( Fashionista . objects . filter ( person__firstname = ' Imelda ' ) ) , 1 )
2010-03-27 07:38:05 +08:00
2011-02-22 11:07:57 +08:00
def test_tabular_non_field_errors ( self ) :
"""
Ensure that non_field_errors are displayed correctly , including the
right value for colspan . Refs #13510.
"""
data = {
' title_set-TOTAL_FORMS ' : 1 ,
' title_set-INITIAL_FORMS ' : 0 ,
' title_set-MAX_NUM_FORMS ' : 0 ,
' _save ' : u ' Save ' ,
' title_set-0-title1 ' : ' a title ' ,
' title_set-0-title2 ' : ' a different title ' ,
}
2011-09-21 02:16:49 +08:00
response = self . client . post ( ' /admin/admin_inlines/titlecollection/add/ ' , data )
2011-02-22 11:07:57 +08:00
# Here colspan is "4": two fields (title1 and title2), one hidden field and the delete checkbock.
self . assertContains ( response , ' <tr><td colspan= " 4 " ><ul class= " errorlist " ><li>The two titles must be the same</li></ul></td></tr> ' )
2011-02-26 09:44:41 +08:00
def test_no_parent_callable_lookup ( self ) :
""" Admin inline `readonly_field` shouldn ' t invoke parent ModelAdmin callable """
# Identically named callable isn't present in the parent ModelAdmin,
# rendering of the add view shouldn't explode
2011-09-21 02:16:49 +08:00
response = self . client . get ( ' /admin/admin_inlines/novel/add/ ' )
2011-02-26 09:44:41 +08:00
self . assertEqual ( response . status_code , 200 )
# View should have the child inlines section
self . assertContains ( response , ' <div class= " inline-group " id= " chapter_set-group " > ' )
def test_callable_lookup ( self ) :
""" Admin inline should invoke local callable when its name is listed in readonly_fields """
2011-09-21 02:16:49 +08:00
response = self . client . get ( ' /admin/admin_inlines/poll/add/ ' )
2011-02-26 09:44:41 +08:00
self . assertEqual ( response . status_code , 200 )
# Add parent object view should have the child inlines section
self . assertContains ( response , ' <div class= " inline-group " id= " question_set-group " > ' )
# The right callabe should be used for the inline readonly_fields
# column cells
self . assertContains ( response , ' <p>Callable in QuestionInline</p> ' )
2011-08-17 22:12:25 +08:00
def test_help_text ( self ) :
"""
Ensure that the inlines ' model field help texts are displayed when
using both the stacked and tabular layouts .
Ref #8190.
"""
2011-09-21 02:16:49 +08:00
response = self . client . get ( ' /admin/admin_inlines/holder4/add/ ' )
2011-08-17 22:12:25 +08:00
self . assertContains ( response , ' <p class= " help " >Awesome stacked help text is awesome.</p> ' , 4 )
2011-08-26 12:36:31 +08:00
self . assertContains ( response , ' <img src= " /static/admin/img/icon-unknown.gif " class= " help help-tooltip " width= " 10 " height= " 10 " alt= " (Awesome tabular help text is awesome.) " title= " Awesome tabular help text is awesome. " /> ' , 1 )
2011-08-17 22:12:25 +08:00
2011-09-21 22:00:58 +08:00
def test_non_related_name_inline ( self ) :
"""
Ensure that multiple inlines with related_name = ' + ' have correct form
prefixes . Bug #16838.
"""
response = self . client . get ( ' /admin/admin_inlines/capofamiglia/add/ ' )
self . assertContains ( response ,
' <input type= " hidden " name= " -1-0-id " id= " id_-1-0-id " /> ' )
self . assertContains ( response ,
' <input type= " hidden " name= " -1-0-capo_famiglia " '
' id= " id_-1-0-capo_famiglia " /> ' )
self . assertContains ( response ,
' <input id= " id_-1-0-name " type= " text " class= " vTextField " '
' name= " -1-0-name " maxlength= " 100 " /> ' )
self . assertContains ( response ,
' <input type= " hidden " name= " -2-0-id " id= " id_-2-0-id " /> ' )
self . assertContains ( response ,
' <input type= " hidden " name= " -2-0-capo_famiglia " '
' id= " id_-2-0-capo_famiglia " /> ' )
self . assertContains ( response ,
' <input id= " id_-2-0-name " type= " text " class= " vTextField " '
' name= " -2-0-name " maxlength= " 100 " /> ' )
2010-03-21 09:54:00 +08:00
class TestInlineMedia ( TestCase ) :
2011-09-21 02:16:49 +08:00
urls = " regressiontests.admin_inlines.urls "
2010-03-21 09:54:00 +08:00
fixtures = [ ' admin-views-users.xml ' ]
def setUp ( self ) :
result = self . client . login ( username = ' super ' , password = ' secret ' )
2010-12-04 15:28:12 +08:00
self . assertEqual ( result , True )
2010-03-21 09:54:00 +08:00
def tearDown ( self ) :
self . client . logout ( )
def test_inline_media_only_base ( self ) :
holder = Holder ( dummy = 13 )
holder . save ( )
Inner ( dummy = 42 , holder = holder ) . save ( )
2011-09-21 02:16:49 +08:00
change_url = ' /admin/admin_inlines/holder/ %i / ' % holder . id
2010-03-21 09:54:00 +08:00
response = self . client . get ( change_url )
self . assertContains ( response , ' my_awesome_admin_scripts.js ' )
def test_inline_media_only_inline ( self ) :
holder = Holder3 ( dummy = 13 )
holder . save ( )
Inner3 ( dummy = 42 , holder = holder ) . save ( )
2011-09-21 02:16:49 +08:00
change_url = ' /admin/admin_inlines/holder3/ %i / ' % holder . id
2010-03-21 09:54:00 +08:00
response = self . client . get ( change_url )
self . assertContains ( response , ' my_awesome_inline_scripts.js ' )
def test_all_inline_media ( self ) :
holder = Holder2 ( dummy = 13 )
holder . save ( )
Inner2 ( dummy = 42 , holder = holder ) . save ( )
2011-09-21 02:16:49 +08:00
change_url = ' /admin/admin_inlines/holder2/ %i / ' % holder . id
2010-03-21 09:54:00 +08:00
response = self . client . get ( change_url )
self . assertContains ( response , ' my_awesome_admin_scripts.js ' )
2010-03-27 07:38:05 +08:00
self . assertContains ( response , ' my_awesome_inline_scripts.js ' )
2010-09-27 23:11:03 +08:00
class TestInlineAdminForm ( TestCase ) :
2011-09-21 02:16:49 +08:00
urls = " regressiontests.admin_inlines.urls "
2010-09-27 23:11:03 +08:00
def test_immutable_content_type ( self ) :
""" Regression for #9362
The problem depends only on InlineAdminForm and its " original "
argument , so we can safely set the other arguments to None / { } . We just
need to check that the content_type argument of Child isn ' t altered by
the internals of the inline form . """
sally = Teacher . objects . create ( name = ' Sally ' )
john = Parent . objects . create ( name = ' John ' )
joe = Child . objects . create ( name = ' Joe ' , teacher = sally , parent = john )
iaf = InlineAdminForm ( None , None , { } , { } , joe )
parent_ct = ContentType . objects . get_for_model ( Parent )
self . assertEqual ( iaf . original . content_type , parent_ct )
2011-10-07 08:41:25 +08:00
class TestInlinePermissions ( TestCase ) :
"""
Make sure the admin respects permissions for objects that are edited
inline . Refs #8060.
"""
urls = " regressiontests.admin_inlines.urls "
def setUp ( self ) :
self . user = User ( username = ' admin ' )
self . user . is_staff = True
self . user . is_active = True
self . user . set_password ( ' secret ' )
self . user . save ( )
self . author_ct = ContentType . objects . get_for_model ( Author )
self . holder_ct = ContentType . objects . get_for_model ( Holder2 )
self . book_ct = ContentType . objects . get_for_model ( Book )
self . inner_ct = ContentType . objects . get_for_model ( Inner2 )
# User always has permissions to add and change Authors, and Holders,
# the main (parent) models of the inlines. Permissions on the inlines
# vary per test.
permission = Permission . objects . get ( codename = ' add_author ' , content_type = self . author_ct )
self . user . user_permissions . add ( permission )
permission = Permission . objects . get ( codename = ' change_author ' , content_type = self . author_ct )
self . user . user_permissions . add ( permission )
permission = Permission . objects . get ( codename = ' add_holder2 ' , content_type = self . holder_ct )
self . user . user_permissions . add ( permission )
permission = Permission . objects . get ( codename = ' change_holder2 ' , content_type = self . holder_ct )
self . user . user_permissions . add ( permission )
author = Author . objects . create ( pk = 1 , name = u ' The Author ' )
author . books . create ( name = u ' The inline Book ' )
self . author_change_url = ' /admin/admin_inlines/author/ %i / ' % author . id
holder = Holder2 . objects . create ( dummy = 13 )
Inner2 . objects . create ( dummy = 42 , holder = holder )
self . holder_change_url = ' /admin/admin_inlines/holder2/ %i / ' % holder . id
self . assertEqual (
self . client . login ( username = ' admin ' , password = ' secret ' ) ,
True )
def tearDown ( self ) :
self . client . logout ( )
def test_inline_add_m2m_noperm ( self ) :
response = self . client . get ( ' /admin/admin_inlines/author/add/ ' )
# No change permission on books, so no inline
self . assertNotContains ( response , ' <h2>Author-book relationships</h2> ' )
self . assertNotContains ( response , ' Add another Author-Book Relationship ' )
self . assertNotContains ( response , ' id= " id_Author_books-TOTAL_FORMS " ' )
def test_inline_add_fk_noperm ( self ) :
response = self . client . get ( ' /admin/admin_inlines/holder2/add/ ' )
# No permissions on Inner2s, so no inline
self . assertNotContains ( response , ' <h2>Inner2s</h2> ' )
self . assertNotContains ( response , ' Add another Inner2 ' )
self . assertNotContains ( response , ' id= " id_inner2_set-TOTAL_FORMS " ' )
def test_inline_change_m2m_noperm ( self ) :
response = self . client . get ( self . author_change_url )
# No change permission on books, so no inline
self . assertNotContains ( response , ' <h2>Author-book relationships</h2> ' )
self . assertNotContains ( response , ' Add another Author-Book Relationship ' )
self . assertNotContains ( response , ' id= " id_Author_books-TOTAL_FORMS " ' )
def test_inline_change_fk_noperm ( self ) :
response = self . client . get ( self . holder_change_url )
# No permissions on Inner2s, so no inline
self . assertNotContains ( response , ' <h2>Inner2s</h2> ' )
self . assertNotContains ( response , ' Add another Inner2 ' )
self . assertNotContains ( response , ' id= " id_inner2_set-TOTAL_FORMS " ' )
def test_inline_add_m2m_add_perm ( self ) :
permission = Permission . objects . get ( codename = ' add_book ' , content_type = self . book_ct )
self . user . user_permissions . add ( permission )
response = self . client . get ( ' /admin/admin_inlines/author/add/ ' )
# No change permission on Books, so no inline
self . assertNotContains ( response , ' <h2>Author-book relationships</h2> ' )
self . assertNotContains ( response , ' Add another Author-Book Relationship ' )
self . assertNotContains ( response , ' id= " id_Author_books-TOTAL_FORMS " ' )
def test_inline_add_fk_add_perm ( self ) :
permission = Permission . objects . get ( codename = ' add_inner2 ' , content_type = self . inner_ct )
self . user . user_permissions . add ( permission )
response = self . client . get ( ' /admin/admin_inlines/holder2/add/ ' )
# Add permission on inner2s, so we get the inline
self . assertContains ( response , ' <h2>Inner2s</h2> ' )
self . assertContains ( response , ' Add another Inner2 ' )
self . assertContains ( response , ' value= " 3 " id= " id_inner2_set-TOTAL_FORMS " ' )
def test_inline_change_m2m_add_perm ( self ) :
permission = Permission . objects . get ( codename = ' add_book ' , content_type = self . book_ct )
self . user . user_permissions . add ( permission )
response = self . client . get ( self . author_change_url )
# No change permission on books, so no inline
self . assertNotContains ( response , ' <h2>Author-book relationships</h2> ' )
self . assertNotContains ( response , ' Add another Author-Book Relationship ' )
self . assertNotContains ( response , ' id= " id_Author_books-TOTAL_FORMS " ' )
self . assertNotContains ( response , ' id= " id_Author_books-0-DELETE " ' )
def test_inline_change_m2m_change_perm ( self ) :
permission = Permission . objects . get ( codename = ' change_book ' , content_type = self . book_ct )
self . user . user_permissions . add ( permission )
response = self . client . get ( self . author_change_url )
# We have change perm on books, so we can add/change/delete inlines
self . assertContains ( response , ' <h2>Author-book relationships</h2> ' )
self . assertContains ( response , ' Add another Author-Book Relationship ' )
self . assertContains ( response , ' value= " 4 " id= " id_Author_books-TOTAL_FORMS " ' )
self . assertContains ( response , ' <input type= " hidden " name= " Author_books-0-id " value= " 1 " ' )
self . assertContains ( response , ' id= " id_Author_books-0-DELETE " ' )
def test_inline_change_fk_add_perm ( self ) :
permission = Permission . objects . get ( codename = ' add_inner2 ' , content_type = self . inner_ct )
self . user . user_permissions . add ( permission )
response = self . client . get ( self . holder_change_url )
# Add permission on inner2s, so we can add but not modify existing
self . assertContains ( response , ' <h2>Inner2s</h2> ' )
self . assertContains ( response , ' Add another Inner2 ' )
# 3 extra forms only, not the existing instance form
self . assertContains ( response , ' value= " 3 " id= " id_inner2_set-TOTAL_FORMS " ' )
self . assertNotContains ( response , ' <input type= " hidden " name= " inner2_set-0-id " value= " 1 " ' )
def test_inline_change_fk_change_perm ( self ) :
permission = Permission . objects . get ( codename = ' change_inner2 ' , content_type = self . inner_ct )
self . user . user_permissions . add ( permission )
response = self . client . get ( self . holder_change_url )
# Change permission on inner2s, so we can change existing but not add new
self . assertContains ( response , ' <h2>Inner2s</h2> ' )
# Just the one form for existing instances
self . assertContains ( response , ' value= " 1 " id= " id_inner2_set-TOTAL_FORMS " ' )
self . assertContains ( response , ' <input type= " hidden " name= " inner2_set-0-id " value= " 1 " ' )
# max-num 0 means we can't add new ones
self . assertContains ( response , ' value= " 0 " id= " id_inner2_set-MAX_NUM_FORMS " ' )
def test_inline_change_fk_add_change_perm ( self ) :
permission = Permission . objects . get ( codename = ' add_inner2 ' , content_type = self . inner_ct )
self . user . user_permissions . add ( permission )
permission = Permission . objects . get ( codename = ' change_inner2 ' , content_type = self . inner_ct )
self . user . user_permissions . add ( permission )
response = self . client . get ( self . holder_change_url )
# Add/change perm, so we can add new and change existing
self . assertContains ( response , ' <h2>Inner2s</h2> ' )
# One form for existing instance and three extra for new
self . assertContains ( response , ' value= " 4 " id= " id_inner2_set-TOTAL_FORMS " ' )
self . assertContains ( response , ' <input type= " hidden " name= " inner2_set-0-id " value= " 1 " ' )
def test_inline_change_fk_change_del_perm ( self ) :
permission = Permission . objects . get ( codename = ' change_inner2 ' , content_type = self . inner_ct )
self . user . user_permissions . add ( permission )
permission = Permission . objects . get ( codename = ' delete_inner2 ' , content_type = self . inner_ct )
self . user . user_permissions . add ( permission )
response = self . client . get ( self . holder_change_url )
# Change/delete perm on inner2s, so we can change/delete existing
self . assertContains ( response , ' <h2>Inner2s</h2> ' )
# One form for existing instance only, no new
self . assertContains ( response , ' value= " 1 " id= " id_inner2_set-TOTAL_FORMS " ' )
self . assertContains ( response , ' <input type= " hidden " name= " inner2_set-0-id " value= " 1 " ' )
self . assertContains ( response , ' id= " id_inner2_set-0-DELETE " ' )
def test_inline_change_fk_all_perms ( self ) :
permission = Permission . objects . get ( codename = ' add_inner2 ' , content_type = self . inner_ct )
self . user . user_permissions . add ( permission )
permission = Permission . objects . get ( codename = ' change_inner2 ' , content_type = self . inner_ct )
self . user . user_permissions . add ( permission )
permission = Permission . objects . get ( codename = ' delete_inner2 ' , content_type = self . inner_ct )
self . user . user_permissions . add ( permission )
response = self . client . get ( self . holder_change_url )
# All perms on inner2s, so we can add/change/delete
self . assertContains ( response , ' <h2>Inner2s</h2> ' )
# One form for existing instance only, three for new
self . assertContains ( response , ' value= " 4 " id= " id_inner2_set-TOTAL_FORMS " ' )
self . assertContains ( response , ' <input type= " hidden " name= " inner2_set-0-id " value= " 1 " ' )
self . assertContains ( response , ' id= " id_inner2_set-0-DELETE " ' )