Friday, March 1, 2019

Re: How to override the ModelAdmin "change" form ?

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

Le vendredi 1 mars 2019 00:06:40 UTC+1, Mike Dewhirst a écrit :
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


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.







--
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.

--
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+unsubscribe@googlegroups.com.
To post to this group, send email to django-users@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/2482cf9f-5ee9-4ae8-9046-daca0d758e8e%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

No comments:

Post a Comment