How to override the ModelAdmin "change" form ?

88 views
Skip to first unread message

karim...@gmail.com

unread,
Feb 28, 2019, 7:35:03 AM2/28/19
to Django users
Hi,

I'm currently struggling with a custom ModelAdmin.

Considering the following model:


# Bloc fonctionnel
class Assembly(Item):

    product = models.ForeignKey(to='ProductFamily', on_delete=models.CASCADE, null=True, verbose_name=_('Famille Produit'))
    functions
= models.ManyToManyField(Function, verbose_name=_('Fonctions'))
   
performances = models.ManyToManyField(Performance, verbose_name=_('Performances'), related_name='performances')

   
def _get_type(self):
       
return ItemType.ASSEMBLY

   
class Meta:
        verbose_name
= _('Bloc Fonctionnel')
        verbose_name_plural
= _('Blocs Fonctionnels')



I have a custom AssemblyAdmin related to it and also a custom AssemblyForm for customizing some fieds.

The performances m2m field is critical.  
The performances are captured in the form with a dynamic_raw_id field, which works fine.

But when this field is modified, some updates/deletions might be applied in other tables of the database.
For this purpose, I need to collect the "performance" pk captured in the html form and compare them with those currently in the database.

Basically, when the user clicks on the regular "Save" or "Save and continue" button, I would need to display an alert form (like when you click on the delete button) to explain what would happen.

I struggled with some ajax routines but it does not work as expected. 

I don't know if it's really doable and how to achieve it.

Any suggestion is welcome.

Cheers.

Z.







Nelson Varela

unread,
Feb 28, 2019, 11:22:47 AM2/28/19
to Django users
You maybe want a model admin view where you can send the user to and collect the data you want to comare and show warnings

Mike Dewhirst

unread,
Feb 28, 2019, 6:06:40 PM2/28/19
to django...@googlegroups.com
On 28/02/2019 9:46 pm, karim...@gmail.com wrote:
Hi,

I'm currently struggling with a custom ModelAdmin.

Karim

I haven't tried to fully understand your use case. However, this is what I think your process could be if you do not wish to ajax it ...

1. Override the model save() method to call a model method which detects your trigger scenario and calls the code you wish to execute to collect all the data you wish to display. This might be in the parent model or the m2m 'through' model. Unlikely to be in the child model.

2. Write a Form to reveal the data you wish to display. It probably needs to be a ModelForm

3. Write a template for the data including any hidden fields for object pks and additionally consider calling {{ block.super }} to display inherited stuff if you are extending another template and using the same block. When I first started to work all this out I was able to get my form to appear at the top of the ModelAdmin form using block.super and spent a bit of time hiding the big red [Delete] button because it was too close to my big blue [Pay now] button. However, as I got deeper into it I somehow lost that and never got it back. I was so pleased with getting it working eventually that I persuaded myself I didn't really want it on the same page anyway. Your mileage may vary :) I think you need to hard-code the form in the ModelAdmin to get it appearing above everything else.

4. Write any necessary urls

5. Write a view to manipulate your data, based on the request and your form

6. Get the Admin to display it on demand. The first line of the change_view() method below initialises the ModelAdmin to do absolutely nothing different than usual. Nothing will happen unless the trigger is detected. Then finally call to super to resume the normal course of events when your code is complete. What follows is my own recent experience. The comments should tell you more than the code

def change_view(self, request, object_id, form_url='', extra_context=None):
    """ self = SubstanceAdmin
        request = wsgi request object
        object_id = substance
        form_url = no idea!
        extra_context = dict of apps, models, admin_urls and permissions

    https://docs.djangoproject.com/en/1.11/ref/contrib/admin/#django.
    contrib.admin.ModelAdmin.change_view 

    """
    # Let the ModelAdmin resume normal operations with its own template
    self.change_form_template = None
    # queryset of m2m records from the 'through' table
    ingredients = Substance_Ingredients.objects.filter(substance_id=object_id)
    subscription = None
    for sm2mi in ingredients:
        # sm2mi.fee_payable() is the detector which triggers the process
        payable, fee_type = sm2mi.fee_payable()  # eg., True, PAID_DATA
        if payable:
            # generate a subscription record with blank token field or
            # if one exists with a non-blank token, return None
            subscription = billing_subscribe(sm2mi, fee_type)
            if subscription:    # collect money for the owner
                # switch the ModelAdmin to the new template
                self.change_form_template = 'payment.html'
                # assemble all the necessary data for the view
                context = billing_collect_context(
                    sm2mi,
                    subscription,
                )
                # get everything into the payment_view context
                if not extra_context:
                    extra_context = dict()
                extra_context.update(self.admin_site.each_context(request))
                extra_context.update(context)
                # wrap the view to protect it with Admin permissions
                self.admin_site.admin_view(
                    # call the view with request and context
                    billing_payment_view(
                        request,
                        sm2mi,
                        subscription,
                        context=extra_context,
                    )
                )
                # only one sm2mi at a time
                break
    return super(SubstanceAdmin, self).change_view(
        request, object_id, form_url, extra_context
    )


 
7. Call super in the model save() method *or* raise an exception to prevent saving. I'm actually not sure about this bit. It may go against the Django flow. However, I do use a BusinessRuleViolation exception which inherits from ValidationError and therefore lets me include a human readable message which appears in the Admin and *seems* to prevent saving. You would need to test this especially with m2m side-effects and atomicity consequences.

I hope this helps.

Mike

--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users...@googlegroups.com.
To post to this group, send email to django...@googlegroups.com.
Visit this group at https://groups.google.com/group/django-users.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/88135218-a965-46c8-a454-c0376a5682f1%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

karim...@gmail.com

unread,
Mar 1, 2019, 5:40:38 AM3/1/19
to Django users
Hi Mike,

Thanks a lot for your feedback.
Your situation is quiet different from mine, BUT the way you override change_view and the related template will certainly help me to achieve what I need.
...and getting rid of dirty and unnecessary ajax calls.

I keep you posted.

Thx.

Karim

karim...@gmail.com

unread,
Mar 1, 2019, 10:08:41 AM3/1/19
to Django users
Hi Mike,

I tried what you wrote by overriding the self.change_form_template but the form does not appear.
Would you please provide me the content of your model admin  billing_payment_view ?

Thanks.

Karim

Le vendredi 1 mars 2019 00:06:40 UTC+1, Mike Dewhirst a écrit :

Mike Dewhirst

unread,
Mar 1, 2019, 4:55:48 PM3/1/19
to django...@googlegroups.com, karim...@gmail.com
On 2/03/2019 2:08 am, karim...@gmail.com wrote:
Hi Mike,

I tried what you wrote by overriding the self.change_form_template but the form does not appear.
Would you please provide me the content of your model admin  billing_payment_view ?

Sure. I recently asked for assistance here and posted most of the code ...

https://groups.google.com/forum/#!topic/django-users/809HBmPa9vk

You are right, my application is different. My template contains a script provided by Stripe which calls their API to collect a credit card payment. But overall it isn't much different than any other template called by any other view. The view just takes a different path depending on whether it is a POST or a GET request. My view looks complex because when the user clicks [Pay now] it happens in the Stripe js embedded in the template and the Stripe API - in turn - hits my view with the actual POST containing the result of the credit card transaction. I had to seed the form/template with hidden data so it travelled across the Stripe boundary and back to me so I could complete the processing. If you can see past that complication it should be clear that I'm using the form to clean the data and the view to interface with the ORM/database.

It is working well for me now and I'm in the middle of writing unit tests.

Good luck

Reply all
Reply to author
Forward
0 new messages