Django Admin Actions on single object

前端 未结 4 835
春和景丽
春和景丽 2021-01-01 22:41

The admin actions seem to work on several items selected in the list view of django admin interface:

In my case I would like to have a simple action button on the ch

相关标签:
4条回答
  • 2021-01-01 22:46

    Create a template for your model in your app.

    templates/admin/<yourapp>/<yourmodel>/change_form.html
    

    With this example content to add a button when changing an existing object.

    {% extends "admin/change_form.html" %}
    {% block submit_buttons_bottom %}
        {{ block.super }}
        {% if original %} {# Only show if changing #}
            <div class="submit-row">
                <a href="{% url 'custom-model-action' original.pk %}">
                     Another action
                </a>
            </div>
        {% endif %}
    {% endblock %}
    

    Link that action to any url and redirect back to your model change object view. More information about extending admin templates.

    Update: Added complete common use case for custom action on existing object

    urls.py

    urlpatterns = [
        url(r'^custom_model_action/(?P<object_pk>\d+)/$',
            core_views.custom_model_action, name='custom-model-action')
    ]
    

    views.py

    from django.urls import reverse
    from django.contrib import messages
    from django.http import HttpResponse, HttpResponseRedirect
    
    def custom_model_action(request, object_pk):
        messages.info(request, 'Performed custom action!')
        return HttpResponseRedirect(
           reverse('admin:<yourapp>_<yourmodel>_change', args=[object_pk])
        )
    
    0 讨论(0)
  • 2021-01-01 22:48

    The built-in admin actions operate on a queryset.

    You can use a calable for the action you whant or to show something else:

    class ProductAdmin(admin.ModelAdmin):
        list_display ('name' )
        readonly_fields('detail_url)
    
    
    def detail_url(self, instance):
        url = reverse('product_detail', kwargs={'pk': instance.slug})
        response = format_html("""<a href="{0}">{0}</a>""", product_detail)
        return response
    

    or using forms

    class ProductForm(forms.Form):
        name = forms.Charfield()
    
        def form_action(self, product, user):
            return Product.value(
                id=product.pk,
                user= user,
            .....
    
            )
    
    
    @admin.register(Product)
    class ProductAdmin(admin.ModelAdmin):
    
    # render buttons and links to
    def product_actions(self, obj):
            return format_html(
                '<a class="button" href="{}">Action1</a>&nbsp;'
                '<a class="button" href="{}">Action 2</a>',
                reverse('admin:product-action-1', args=[obj.pk]),
                reverse('admin:aproduct-action-3', args=[obj.pk]),
            )
    

    for more details about using forms

    0 讨论(0)
  • 2021-01-01 22:52

    If you realy need per-single object, I suggest you to use this solution, eg:

    class Gallery(TimeStampedModel):
        title = models.CharField(max_length=200)
        attachment = models.FileField(upload_to='gallery/attachment/%Y/%m/%d')
    
        def __str__(self):
            return self.title
    
        def process_button(self):
            return ('<button id="%(id)s class="btn btn-default process_btn" '
                    'data-value="%(value)s>Process</button>' % {'id': self.pk, 'value': self.attachment.url})
        process_button.short_description = 'Action'
        process_button.allow_tags = True
    

    In your admin.py, insert process_button into list_display;

    class GalleryAdmin(admin.ModelAdmin):
        list_display = ['title', 'process_button', 'created']
        search_fields = ['title', 'pk']
        ....
    
        class Media:
            js = ('path/to/yourfile.js', )
    

    Then, inside yourfile.js, you can also process it..

    $('.process_btn').click(function(){
        var id = $(this).attr('id');       // single object id
        var value = $(this).data('value'); // single object value
        ...
    });
    

    Hope it helpful..

    0 讨论(0)
  • 2021-01-01 23:10

    Not the same as the topic starter asked, but this snippet allows to have Single Object action from on the list page with minimum amount of code

    BaseAction code

    class AdminActionError(Exception):
        pass
    
    
    class AdminObjectAction:
        """Base class for Django Admin actions for single object"""
    
        short_description = None
        exp_obj_state = {}
    
        def __init__(self, modeladmin, request, queryset):
            self.admin = modeladmin
            self.request = request
            self.queryset = queryset
            self.__call__()
    
        def validate_qs(self):
            count = self.queryset.count()
            if count != 1:
                self.error("You must select one object for this action.")
    
            if self.exp_obj_state:
                if self.queryset.filter(**self.exp_obj_state).count() != 1:
                    self.error(f'Selected object does not meet the requirements: {self.exp_obj_state}')
    
        def error(self, msg):
            raise AdminActionError(msg)
    
        def get_object(self):
            return self.queryset.get()
    
        def process_object_action(self, obj):
            pass
    
        def validate_obj(self, obj):
            pass
    
        def __call__(self, *args, **kwargs):
            try:
                self.validate_qs()
                obj = self.get_object()
                self.validate_obj(obj)
            except AdminActionError as e:
                self.admin.message_user(self.request, f"Failed: {e}", level=messages.ERROR)
            else:
                with transaction.atomic():
                    result = self.process_object_action(obj)
    
                self.admin.message_user(self.request, f"Success: {self.short_description}, {result}")
    
    

    Custom Action [minimum amount of code]

    class RenewSubscriptionAction(AdminObjectAction):
        short_description = 'Renew subscription'
    
        exp_obj_state = {
            'child': None,
            'active_status': True,        
        }
    
        def process_object_action(self, obj):
            manager = RenewManager(user=obj.user, subscription=obj)
            return manager.process()
    
    

    AdminClass

    class SomeAdmin(admin.ModelAdmin):
        actions = [RenewSubscriptionAction]
    
    0 讨论(0)
提交回复
热议问题