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