Ticket #8261: view_on_site.diff

File view_on_site.diff, 16.9 KB (added by Ivan Giuliani, 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):  
    103103
    104104    def __iter__(self):
    105105        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)
    107108        for form in self.formset.extra_forms:
    108109            yield InlineAdminForm(self.formset, form, self.fieldsets, self.opts.prepopulated_fields, None)
    109110
    class InlineAdminForm(AdminForm):  
    125126    """
    126127    A wrapper around an inline form for use in the admin system.
    127128    """
    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):
    129130        self.formset = formset
    130131        self.original = original
    131132        if original is not None:
    132133            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
    134136        super(InlineAdminForm, self).__init__(form, fieldsets, prepopulated_fields)
    135137   
    136138    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):  
    3838    filter_horizontal = ()
    3939    radio_fields = {}
    4040    prepopulated_fields = {}
     41    view_on_site = False
    4142
    4243    def formfield_for_dbfield(self, db_field, **kwargs):
    4344        """
    class BaseModelAdmin(object):  
    151152        return None
    152153    declared_fieldsets = property(_declared_fieldsets)
    153154
     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
    154175class ModelAdmin(BaseModelAdmin):
    155176    "Encapsulates all admin options and functionality for a given model."
    156177    __metaclass__ = forms.MediaDefiningClass
    class ModelAdmin(BaseModelAdmin):  
    392413            'has_change_permission': self.has_change_permission(request, obj),
    393414            'has_delete_permission': self.has_delete_permission(request, obj),
    394415            '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),
    396418            'ordered_objects': ordered_objects,
    397419            'form_url': mark_safe(form_url),
    398420            '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  
    2525{% block object-tools %}
    2626{% if change %}{% if not is_popup %}
    2727  <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%}
    2929  </ul>
    3030{% endif %}{% endif %}
    3131{% 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  
    1010    {% 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 %}
    1111  </h3>
    1212  {% 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>
    1414  {% endif %}
    1515  {% if inline_admin_form.form.non_field_errors %}{{ inline_admin_form.form.non_field_errors }}{% endif %}
    1616
  • 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  
    2424        <td class="original">
    2525          {% if inline_admin_form.original or inline_admin_form.show_url %}<p>
    2626          {% 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 %}
    2828            </p>{% endif %}
    2929          {{ inline_admin_form.pk_field.field }} {{ inline_admin_form.fk_field.field }}
    3030          {% 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):  
    232232            for idx, f in enumerate(val):
    233233                get_field(cls, model, opts, "prepopulated_fields['%s'][%d]" % (field, idx), f)
    234234
     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
    235240def check_isseq(cls, label, obj):
    236241    if not isinstance(obj, (list, tuple)):
    237242        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:  
    597597    Performs a full-text match. This is like the default search method but uses
    598598    an index. Currently this is only available for MySQL.
    599599
     600``view_on_site``
     601~~~~~~~~~~~~~~~~
     602
     603.. versionadded:: 1.1
     604
     605Set ``view_on_site`` to control whether to display or not the "View on site" link.
     606This link should bring you to a URL where you can display the saved object.
     607
     608Example::
     609
     610    class PersonAdmin(admin.ModelAdmin):
     611        view_on_site = True
     612
     613This value can be either a boolean flag or a callable. Default is ``False``.
     614
     615In case it is a callable, it accepts one parameter for the model instance.
     616For 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
    600622``ModelAdmin`` methods
    601623----------------------
    602624
  • 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):  
    2020
    2121    def __unicode__(self):
    2222        return self.title
    23    
     23
    2424    def model_year(self):
    2525        return self.date.year
    2626    model_year.admin_order_field = 'date'
    class ChapterInline(admin.TabularInline):  
    7979class ArticleAdmin(admin.ModelAdmin):
    8080    list_display = ('content', 'date', callable_year, 'model_year', 'modeladmin_year')
    8181    list_filter = ('date',)
     82    view_on_site = False
    8283
    8384    def changelist_view(self, request):
    8485        "Test that extra_context works"
    class Thing(models.Model):  
    134135class ThingAdmin(admin.ModelAdmin):
    135136    list_filter = ('color',)
    136137
     138class City(models.Model):
     139    name = models.CharField(max_length=100)
     140
     141class Restaurant(models.Model):
     142    city = models.ForeignKey(City)
     143    name = models.CharField(max_length=100)
     144
     145class Worker(models.Model):
     146    work_at = models.ForeignKey(Restaurant)
     147    name = models.CharField(max_length=50)
     148    surname = models.CharField(max_length=50)
     149
     150class RestaurantInlineAdmin(admin.TabularInline):
     151    model = Restaurant
     152    view_on_site = True
     153
     154class CityAdmin(admin.ModelAdmin):
     155    inlines = [ RestaurantInlineAdmin ]
     156    view_on_site = True
     157
     158class WorkerAdmin(admin.ModelAdmin):
     159    def view_on_site(self, obj):
     160        return '/worker/%s/%s/' % (obj.surname, obj.name)
     161
     162class WorkerInlineAdmin(admin.TabularInline):
     163    model = Worker
     164    def view_on_site(self, obj):
     165        return '/worker_inline/%s/%s/' % (obj.surname, obj.name)
     166
     167class RestaurantAdmin(admin.ModelAdmin):
     168    inlines = [ WorkerInlineAdmin ]
     169    view_on_site = False
     170
    137171admin.site.register(Article, ArticleAdmin)
    138172admin.site.register(CustomArticle, CustomArticleAdmin)
    139173admin.site.register(Section, inlines=[ArticleInline])
    140174admin.site.register(ModelWithStringPrimaryKey)
    141175admin.site.register(Color)
    142176admin.site.register(Thing, ThingAdmin)
     177admin.site.register(City, CityAdmin)
     178admin.site.register(Restaurant, RestaurantAdmin)
     179admin.site.register(Worker, WorkerAdmin)
    143180
    144181# We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2.
    145182# 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  
    99from django.utils.html import escape
    1010
    1111# local test models
    12 from models import Article, CustomArticle, Section, ModelWithStringPrimaryKey
     12from models import Article, CustomArticle, Section, ModelWithStringPrimaryKey, City, Restaurant, Worker
    1313
    1414class AdminViewBasicTest(TestCase):
    1515    fixtures = ['admin-views-users.xml', 'admin-views-colors.xml']
    class AdminViewUnicodeTest(TestCase):  
    735735        self.failUnlessEqual(response.status_code, 200)
    736736        response = self.client.post('/test_admin/admin/admin_views/book/1/delete/', delete_dict)
    737737        self.assertRedirects(response, '/test_admin/admin/admin_views/book/')
     738
     739class 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
     772class 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
Back to Top