简体   繁体   中英

How would I send an alert or message to a view from a Django model's (overridden) save( ) function?

CONTEXT:

  1. A model object's user_account_granted account indicates if a model object is linked to an non null user account.
  2. When user_account_granted changes from False to True, I detect this in the overridden save() function. Here, I successfully make a User, pulling arguments (email, username, name, etc) from the model object
  3. I create a password and send the new account login info to the object's email
  4. If the email failed, I delete the account

PROBLEM:

I want to alert the current user (who just submitted the form triggering the save() ) that the email was either successful (and the new account now exists) or unsuccessful (and no new account is created). I cannot use the Django Messaging Framework in the save() function, as it requires the request. What can I do?

    def save(self, *args, **kwargs):

      if self.id:
        previous_fields = MyModel(pk=self.id)
        if previous_fields.user_account_granted != self.user_account_granted:
            title = "Here's your account!"
            if previous_fields.user_account_granted == False and self.user_account_granted == True:
                user = User(
                    username=self.first_name + "." + self.last_name,
                    email=self.email,
                    first_name=self.first_name,
                    last_name=self.last_name
                )
                random_password = User.objects.make_random_password() #this gets hashed on user create
                user.set_password(random_password)
                user.save()

                self.user = user
                message = "You have just been given an account! \n\n Here's your account info: \nemail: " + self.user.email + "\npassword: " + random_password
            if previous_fields.user_account_granted == True and self.user_account_granted == False:
                message = "You no longer have an account. Sorry :( "
            try: 
                sent_success = send_mail(title, message, 'example@email.com', [self.email], fail_silently=False)

                if sent_success == 1:
                    ##HERE I WANT TO INDICATE EMAIL SENT SUCCESS TO THE USER'S VIEW AFTER THE FORM IS SUBMITTED
                else:
                    ##HERE I WANT TO INDICATE EMAIL SENT FAILURE TO THE USER'S VIEW AFTER THE FORM IS SUBMITTED
                    user.delete()
                    self.user_account_granted = False
            except:
                ##HERE I WANT TO INDICATE EMAIL SENT FAILURE TO THE USER'S VIEW AFTER THE FORM IS SUBMITTED
                user.delete()
                self.user_account_granted = False

       super(MyModel, self).save(*args, **kwargs)

You don't send an email in a model's save() function. Ever. That's just not its purpose. Consider:

  • save() may be called from the shell.
  • save() may be called from wherever in your project.

The purpose of the save() method is to save the object. End of story.

Now. Let's get back to what you really want to achieve. You are in the process of handling a form submitted by the user. Which means you're dealing with at least to other things here: the form and the view.

Let's have a closer look at what their purpose is:

  • The Form's basic role is to encapsulate data input and validation. It can be extended so as to encompass the full role of a Command . After all, it only misses an execute() function.

  • The View's basic role is to take action based on the browser's request (here, the POSTing of a form) and trigger the displaying of the result.

You may choose either. You could have an execute() method on your form, and just call that from you view. Or you could have the view take action after checking that the form is_valid() . I would personnaly choose the form for the actual action, and the view for showing the result.

Thus, in my view, I would customize the form_valid() method to call execute() and do whatever is needed depending on the result. That is:

class MyForm(Form):
    # whatever you already have there
    def clean(self):
        # unrelated to your issue, but just reminding you
        # this is the place you make sure everything is right
        # This very method MUST raise an error if any input or any
        # other condition already known at that point is not fulfilled.
        if you_do_not_want_to_grand_an_account:
            raise ValidationError('Account not granted, because.')
        return self.cleaned_data

    def execute(self):
        do_whatever()
        needs_to_be_done()
        if it_failed:
            raise AnAppropriateError(_('Descriptive message'))

class MyView(FormView):
    form = MyForm
    # your other view stuff here

    def form_valid(self, form):
        try:
            form.execute()
        except AnAppropriateError as err:
            messages.add_message(self.request, messages.ERROR, err.message)
        else:
            messages.add_message(self.request, messages.INFO, _('It worked'))

AnAppropriateError may just be RuntimeError if you want it quick and dirty, but you should define your own exception class.

Also, you may want to decorate the execute()̀ method with @transaction.atomic()`, depending on its content.

As a final note, remember you cannot be sure the email was actually sent. It's common to have mail systems that accept emails even if there is some error. You'll only know when it bounces, several days later.

First of all it is really not a good idea to send email from model classes. I highly object that. It is better to do that in your form

But you have already implemented that.

There is a way you can access the request inside a model, I wouldn't recommend it though. There is a plugin called CRequest Middlewar, https://pypi.python.org/pypi/django-crequest

You can use this middleware to access the request anywhere new like.

$ python pip install django-crequest

First import the crequest's middleware:

from crequest.middleware import CrequestMiddleware

Get the current request ;):

current_request = CrequestMiddleware.get_request()

Done. Now you can use the message framework as you wish in that model.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM