Ticket #8261: view_on_site.diff
File view_on_site.diff, 16.9 KB (added by , 16 years ago) |
---|
-
django/contrib/admin/helpers.py
diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py index aaa2e30..334db29 100644
a b class InlineAdminFormSet(object): 103 103 104 104 def __iter__(self): 105 105 for form, original in zip(self.formset.initial_forms, self.formset.get_queryset()): 106 yield InlineAdminForm(self.formset, form, self.fieldsets, self.opts.prepopulated_fields, original) 106 view_url = self.opts.get_view_on_site_url(original) 107 yield InlineAdminForm(self.formset, form, self.fieldsets, self.opts.prepopulated_fields, original, view_url) 107 108 for form in self.formset.extra_forms: 108 109 yield InlineAdminForm(self.formset, form, self.fieldsets, self.opts.prepopulated_fields, None) 109 110 … … class InlineAdminForm(AdminForm): 125 126 """ 126 127 A wrapper around an inline form for use in the admin system. 127 128 """ 128 def __init__(self, formset, form, fieldsets, prepopulated_fields, original ):129 def __init__(self, formset, form, fieldsets, prepopulated_fields, original, view_on_site_url=None): 129 130 self.formset = formset 130 131 self.original = original 131 132 if original is not None: 132 133 self.original.content_type_id = ContentType.objects.get_for_model(original).pk 133 self.show_url = original and hasattr(original, 'get_absolute_url') 134 self.show_url = original and view_on_site_url is not None 135 self.absolute_url = view_on_site_url 134 136 super(InlineAdminForm, self).__init__(form, fieldsets, prepopulated_fields) 135 137 136 138 def __iter__(self): -
django/contrib/admin/options.py
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 3d60b9d..360504a 100644
a b class BaseModelAdmin(object): 38 38 filter_horizontal = () 39 39 radio_fields = {} 40 40 prepopulated_fields = {} 41 view_on_site = False 41 42 42 43 def formfield_for_dbfield(self, db_field, **kwargs): 43 44 """ … … class BaseModelAdmin(object): 151 152 return None 152 153 declared_fieldsets = property(_declared_fieldsets) 153 154 155 def get_view_on_site_url(self, obj=None): 156 if obj is None or (hasattr(self, 'view_on_site') and not self.view_on_site): 157 return None 158 159 if callable(self.view_on_site): 160 return self.view_on_site(obj) 161 elif type(self.view_on_site) == bool and self.view_on_site: 162 # in case we have overriden the default view_on_site with a 163 # boolean flag, revert to the ContentType lookup 164 return self._view_on_site(obj) 165 166 return None 167 168 def _view_on_site(self, obj): 169 content_type_id, object_id = ContentType.objects.get_for_model(obj).pk, obj.pk 170 171 return "../../../r/%(content_type_id)s/%(object_id)s/" % ({ 172 'content_type_id': content_type_id, 173 'object_id': object_id}) 174 154 175 class ModelAdmin(BaseModelAdmin): 155 176 "Encapsulates all admin options and functionality for a given model." 156 177 __metaclass__ = forms.MediaDefiningClass … … class ModelAdmin(BaseModelAdmin): 392 413 'has_change_permission': self.has_change_permission(request, obj), 393 414 'has_delete_permission': self.has_delete_permission(request, obj), 394 415 'has_file_field': True, # FIXME - this should check if form or formsets have a FileField, 395 'has_absolute_url': hasattr(self.model, 'get_absolute_url'), 416 'has_absolute_url': self.get_view_on_site_url(obj) is not None, 417 'absolute_url': self.get_view_on_site_url(obj), 396 418 'ordered_objects': ordered_objects, 397 419 'form_url': mark_safe(form_url), 398 420 'opts': opts, -
django/contrib/admin/templates/admin/change_form.html
diff --git a/django/contrib/admin/templates/admin/change_form.html b/django/contrib/admin/templates/admin/change_form.html index 2fb17bb..052d75e 100644
a b 25 25 {% block object-tools %} 26 26 {% if change %}{% if not is_popup %} 27 27 <ul class="object-tools"><li><a href="history/" class="historylink">{% trans "History" %}</a></li> 28 {% if has_absolute_url %}<li><a href=" ../../../r/{{ content_type_id }}/{{ object_id }}/" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%}28 {% if has_absolute_url %}<li><a href="{{ absolute_url }}" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%} 29 29 </ul> 30 30 {% endif %}{% endif %} 31 31 {% endblock %} -
django/contrib/admin/templates/admin/edit_inline/stacked.html
diff --git a/django/contrib/admin/templates/admin/edit_inline/stacked.html b/django/contrib/admin/templates/admin/edit_inline/stacked.html index 9d9f598..f43e7e0 100644
a b 10 10 {% if inline_admin_formset.formset.can_delete and inline_admin_form.original %}<span class="delete">{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}</span>{% endif %} 11 11 </h3> 12 12 {% if inline_admin_form.show_url %} 13 <p><a href=" ../../../r/{{ inline_admin_form.original.content_type_id }}/{{ inline_admin_form.original.id }}/">{% trans "View on site" %}</a></p>13 <p><a href="{{ inline_admin_form.absolute_url }}">{% trans "View on site" %}</a></p> 14 14 {% endif %} 15 15 {% if inline_admin_form.form.non_field_errors %}{{ inline_admin_form.form.non_field_errors }}{% endif %} 16 16 -
django/contrib/admin/templates/admin/edit_inline/tabular.html
diff --git a/django/contrib/admin/templates/admin/edit_inline/tabular.html b/django/contrib/admin/templates/admin/edit_inline/tabular.html index 820928a..7d6cc2f 100644
a b 24 24 <td class="original"> 25 25 {% if inline_admin_form.original or inline_admin_form.show_url %}<p> 26 26 {% if inline_admin_form.original %} {{ inline_admin_form.original }}{% endif %} 27 {% if inline_admin_form.show_url %}<a href=" ../../../r/{{ inline_admin_form.original.content_type_id }}/{{ inline_admin_form.original.id }}/">{% trans "View on site" %}</a>{% endif %}27 {% if inline_admin_form.show_url %}<a href="{{ inline_admin_form.absolute_url }}">{% trans "View on site" %}</a>{% endif %} 28 28 </p>{% endif %} 29 29 {{ inline_admin_form.pk_field.field }} {{ inline_admin_form.fk_field.field }} 30 30 {% spaceless %} -
django/contrib/admin/validation.py
diff --git a/django/contrib/admin/validation.py b/django/contrib/admin/validation.py index ccade8a..faf70c7 100644
a b def validate_base(cls, model): 232 232 for idx, f in enumerate(val): 233 233 get_field(cls, model, opts, "prepopulated_fields['%s'][%d]" % (field, idx), f) 234 234 235 # view_on_site 236 if hasattr(cls, 'view_on_site'): 237 if not callable(cls.view_on_site) and not isinstance(cls.view_on_site, bool): 238 raise ImproperlyConfigured("%s.view_on_site is not a callable or a boolean value." % cls.__name__) 239 235 240 def check_isseq(cls, label, obj): 236 241 if not isinstance(obj, (list, tuple)): 237 242 raise ImproperlyConfigured("'%s.%s' must be a list or tuple." % (cls.__name__, label)) -
docs/ref/contrib/admin.txt
diff --git a/docs/ref/contrib/admin.txt b/docs/ref/contrib/admin.txt index f24dc46..3373f9c 100644
a b with an operator: 597 597 Performs a full-text match. This is like the default search method but uses 598 598 an index. Currently this is only available for MySQL. 599 599 600 ``view_on_site`` 601 ~~~~~~~~~~~~~~~~ 602 603 .. versionadded:: 1.1 604 605 Set ``view_on_site`` to control whether to display or not the "View on site" link. 606 This link should bring you to a URL where you can display the saved object. 607 608 Example:: 609 610 class PersonAdmin(admin.ModelAdmin): 611 view_on_site = True 612 613 This value can be either a boolean flag or a callable. Default is ``False``. 614 615 In case it is a callable, it accepts one parameter for the model instance. 616 For example:: 617 618 class PersonAdmin(admin.ModelAdmin): 619 def view_on_site(self, obj): 620 return '/show/%s/%s/' % (obj.first_name, obj.last_name) 621 600 622 ``ModelAdmin`` methods 601 623 ---------------------- 602 624 -
new file tests/regressiontests/admin_views/fixtures/admin-views-restaurants.xml
diff --git a/tests/regressiontests/admin_views/fixtures/admin-views-restaurants.xml b/tests/regressiontests/admin_views/fixtures/admin-views-restaurants.xml new file mode 100644 index 0000000..fcb2efb
- + 1 <?xml version="1.0" encoding="utf-8"?> 2 <django-objects version="1.0"> 3 <object pk="1" model="admin_views.city"> 4 <field type="CharField" name="name">New York</field> 5 </object> 6 <object pk="2" model="admin_views.city"> 7 <field type="CharField" name="name">Chicago</field> 8 </object> 9 <object pk="3" model="admin_views.city"> 10 <field type="CharField" name="name">San Francisco</field> 11 </object> 12 <object pk="1" model="admin_views.restaurant"> 13 <field to="admin_views.city" name="city" rel="ManyToOneRel">1</field> 14 <field type="CharField" name="name">Italian Pizza</field> 15 </object> 16 <object pk="2" model="admin_views.restaurant"> 17 <field to="admin_views.city" name="city" rel="ManyToOneRel">1</field> 18 <field type="CharField" name="name">Boulevard</field> 19 </object> 20 <object pk="3" model="admin_views.restaurant"> 21 <field to="admin_views.city" name="city" rel="ManyToOneRel">2</field> 22 <field type="CharField" name="name">Chinese Dinner</field> 23 </object> 24 <object pk="4" model="admin_views.restaurant"> 25 <field to="admin_views.city" name="city" rel="ManyToOneRel">2</field> 26 <field type="CharField" name="name">Angels</field> 27 </object> 28 <object pk="5" model="admin_views.restaurant"> 29 <field to="admin_views.city" name="city" rel="ManyToOneRel">2</field> 30 <field type="CharField" name="name">Take Away</field> 31 </object> 32 <object pk="6" model="admin_views.restaurant"> 33 <field to="admin_views.city" name="city" rel="ManyToOneRel">3</field> 34 <field type="CharField" name="name">The Unknown Restaurant</field> 35 </object> 36 <object pk="1" model="admin_views.worker"> 37 <field to="admin_views.restaurant" name="work_at" rel="ManyToOneRel">1</field> 38 <field type="CharField" name="name">Mario</field> 39 <field type="CharField" name="surname">Rossi</field> 40 </object> 41 <object pk="2" model="admin_views.worker"> 42 <field to="admin_views.restaurant" name="work_at" rel="ManyToOneRel">1</field> 43 <field type="CharField" name="name">Antonio</field> 44 <field type="CharField" name="surname">Bianchi</field> 45 </object> 46 <object pk="3" model="admin_views.worker"> 47 <field to="admin_views.restaurant" name="work_at" rel="ManyToOneRel">1</field> 48 <field type="CharField" name="name">John</field> 49 <field type="CharField" name="surname">Doe</field> 50 </object> 51 </django-objects> -
tests/regressiontests/admin_views/models.py
diff --git a/tests/regressiontests/admin_views/models.py b/tests/regressiontests/admin_views/models.py index 050823f..8b001d8 100644
a b class Article(models.Model): 20 20 21 21 def __unicode__(self): 22 22 return self.title 23 23 24 24 def model_year(self): 25 25 return self.date.year 26 26 model_year.admin_order_field = 'date' … … class ChapterInline(admin.TabularInline): 79 79 class ArticleAdmin(admin.ModelAdmin): 80 80 list_display = ('content', 'date', callable_year, 'model_year', 'modeladmin_year') 81 81 list_filter = ('date',) 82 view_on_site = False 82 83 83 84 def changelist_view(self, request): 84 85 "Test that extra_context works" … … class Thing(models.Model): 134 135 class ThingAdmin(admin.ModelAdmin): 135 136 list_filter = ('color',) 136 137 138 class City(models.Model): 139 name = models.CharField(max_length=100) 140 141 class Restaurant(models.Model): 142 city = models.ForeignKey(City) 143 name = models.CharField(max_length=100) 144 145 class Worker(models.Model): 146 work_at = models.ForeignKey(Restaurant) 147 name = models.CharField(max_length=50) 148 surname = models.CharField(max_length=50) 149 150 class RestaurantInlineAdmin(admin.TabularInline): 151 model = Restaurant 152 view_on_site = True 153 154 class CityAdmin(admin.ModelAdmin): 155 inlines = [ RestaurantInlineAdmin ] 156 view_on_site = True 157 158 class WorkerAdmin(admin.ModelAdmin): 159 def view_on_site(self, obj): 160 return '/worker/%s/%s/' % (obj.surname, obj.name) 161 162 class WorkerInlineAdmin(admin.TabularInline): 163 model = Worker 164 def view_on_site(self, obj): 165 return '/worker_inline/%s/%s/' % (obj.surname, obj.name) 166 167 class RestaurantAdmin(admin.ModelAdmin): 168 inlines = [ WorkerInlineAdmin ] 169 view_on_site = False 170 137 171 admin.site.register(Article, ArticleAdmin) 138 172 admin.site.register(CustomArticle, CustomArticleAdmin) 139 173 admin.site.register(Section, inlines=[ArticleInline]) 140 174 admin.site.register(ModelWithStringPrimaryKey) 141 175 admin.site.register(Color) 142 176 admin.site.register(Thing, ThingAdmin) 177 admin.site.register(City, CityAdmin) 178 admin.site.register(Restaurant, RestaurantAdmin) 179 admin.site.register(Worker, WorkerAdmin) 143 180 144 181 # We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2. 145 182 # That way we cover all four cases: -
tests/regressiontests/admin_views/tests.py
diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index 391d1ff..1663c34 100644
a b from django.contrib.admin.util import quote 9 9 from django.utils.html import escape 10 10 11 11 # local test models 12 from models import Article, CustomArticle, Section, ModelWithStringPrimaryKey 12 from models import Article, CustomArticle, Section, ModelWithStringPrimaryKey, City, Restaurant, Worker 13 13 14 14 class AdminViewBasicTest(TestCase): 15 15 fixtures = ['admin-views-users.xml', 'admin-views-colors.xml'] … … class AdminViewUnicodeTest(TestCase): 735 735 self.failUnlessEqual(response.status_code, 200) 736 736 response = self.client.post('/test_admin/admin/admin_views/book/1/delete/', delete_dict) 737 737 self.assertRedirects(response, '/test_admin/admin/admin_views/book/') 738 739 class AdminViewOnSiteTest(TestCase): 740 fixtures = ['admin-views-users.xml', 'admin-views-restaurants.xml'] 741 742 def setUp(self): 743 self.client.login(username='super', password='secret') 744 745 def tearDown(self): 746 self.client.logout() 747 748 def test_false(self): 749 "Ensure that the 'View on site' button is not displayed if view_on_site is False" 750 response = self.client.get('/test_admin/admin/admin_views/restaurant/1/') 751 content_type_pk = ContentType.objects.get_for_model(Restaurant).pk 752 self.failIf( 753 '"../../../r/%s/1/"' % content_type_pk in response.content, 754 '"View on site" button displayed even if it has been disabled.') 755 756 def test_true(self): 757 "Ensure that the default behaviour is followed if view_on_site is True" 758 response = self.client.get('/test_admin/admin/admin_views/city/1/') 759 content_type_pk = ContentType.objects.get_for_model(City).pk 760 self.failUnless( 761 '"../../../r/%s/1/"' % content_type_pk in response.content, 762 '"View on site" is enabled but has not been displayed.') 763 764 def test_callable(self): 765 "Ensure that the right link is displayed if view_on_site is a callable" 766 response = self.client.get('/test_admin/admin/admin_views/worker/1/') 767 worker = Worker.objects.get(pk=1) 768 self.failUnless( 769 '"/worker/%s/%s/"' % (worker.surname, worker.name) in response.content, 770 '"View on site" is defined but has not been displayed.') 771 772 class InlineAdminViewOnSiteTest(TestCase): 773 fixtures = ['admin-views-users.xml', 'admin-views-restaurants.xml'] 774 775 def setUp(self): 776 self.client.login(username='super', password='secret') 777 778 def tearDown(self): 779 self.client.logout() 780 781 def test_true(self): 782 "Ensure that the 'View on site' button is displayed if view_on_site is True" 783 response = self.client.get('/test_admin/admin/admin_views/city/1/') 784 content_type_pk = ContentType.objects.get_for_model(Restaurant).pk 785 self.failUnless( 786 '../../../r/%s/1/' % content_type_pk in response.content, 787 '"View on site" is enabled but has not been displayed.') 788 789 def test_callable(self): 790 "Ensure that the default behaviour is followed if view_on_site is True" 791 response = self.client.get('/test_admin/admin/admin_views/restaurant/1/') 792 content_type_pk = ContentType.objects.get_for_model(Worker).pk 793 worker = Worker.objects.get(pk=1) 794 self.failUnless( 795 '"/worker_inline/%s/%s/"' % (worker.surname, worker.name) in response.content, 796 '"View on site" is defined but has not been displayed.') 797