Send email each follower

I want to send email to each follower after create item. For this I use the package (Django Lifecycle Hooks). But I think it doesn’t matter.
I am not certain how to connect between follower models and book models (new item).

models.py

class Book(models.Models):
   title = models.CharField(max_length=255)
   author = models.ForeignKey(
        "users.CustomUser", on_delete=models.SET_NULL, null=True
    )
  
# This is my try. But it doesn't work as expected

    @hook(AFTER_CREATE)
    def send_followers(self):
          followers = CustomUser.objects.filter(
            followers__follower__in=follower, followers__following=self.author.profile)
          if CustomUser.objects.filter(
            followers__follower__in=CustomUser.objects.only(
                'followers__follower'),
            followers__following=self.author.profile
        ).exists():
            send_mail(
                "Article published",
                "Item was created",
                "host__email",
                [followers],
                fail_silently=False,
            )
        else:
            pass

class Followers(models.Model):
    follower = models.ForeignKey(
        "users.CustomUser", on_delete=models.CASCADE, null=True, blank=True)
    following = models.ForeignKey(
        "Profile", on_delete=models.CASCADE, null=True, blank=True)
    created = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return '%s, watch: %s, %s' % (
            self.follower,
            self.following,
            self.created
        )


class CustomUser(AbstractUser):
    gender = models.ForeignKey(
        "Gender", on_delete=models.CASCADE, blank=True, null=True
    )

class Profile(models.Model):
    slug = models.SlugField(unique=True)
    user = models.OneToOneField(get_user_model(), on_delete=models.CASCADE)

views.py

class BookCreateView(LoginRequiredMixin, CreateView):
    model = Book
    form_class = BookForm
    template_name = "book/example.html"
  
        def form_valid(self, form):
        form.instance.author = self.request.user
        return super(BookCreateView, self).form_valid(form)

    def post(self, request, *args, **kwargs):
        form = self.get_form()
        if form.is_valid():
             obj = form.save(commit=False)
             obj.author = self.request.user
             obj.save()
             form.save_m2m()
             return redirect("/")
       return self.render_to_response({"form": form})

urls.py

urlpatterns = [
    path("", HomePageView.as_view(), name="home"),
    path("book/create", BookCreateView.as_view(), name="book_create"),

This actually becomes a lot easier if you do this right in your View and not in the handler.
Why? Because in the view, you have access to the user who posted the book, and getting the followers for a user is more direct than starting from the book.

But aside from that, I find your model structure a bit confusing. Your Followers model has foreign keys to two different models - which appear to be related to each other via a OneToOneField.

Anyway, if I’m understanding the intent of both your statement and your models, the set of CustomUser who are Followers (via the follower field) of another CustomUser would be:
CustomUser.followers_set.all()
This gives you the list of Followers of a CustomUser. Then, you can retrieve the email address for each CustomUser through the forward link. (e.g. custom_user.follower.email)

Ken, hello!
Thank you for the response. I rebuilt my Followers models as you wrote me. Thank you! Now I suppose it will be more right than before I sent.

class Followers(models.Model):
    follower = models.ForeignKey(
        "users.CustomUser", on_delete=models.CASCADE, null=True, blank=True, related_name='follower')
    following = models.ForeignKey(
        "users.CustomUser", on_delete=models.CASCADE, null=True, blank=True, related_name='following')
    created = models.DateTimeField(auto_now_add=True)

Also, I want to add send email by view after create an item. But I changed my models, and now I don’t know how to build this system. Can you give any advice?

views.py

class BookCreateView(LoginRequiredMixin, CreateView):
    model = Book
    form_class = BookForm
    template_name = "book/example.html"
  
        def form_valid(self, form):
        form.instance.author = self.request.user
        return super(BookCreateView, self).form_valid(form)

    def post(self, request, *args, **kwargs):
        form = self.get_form()
        if form.is_valid():
             obj = form.save(commit=False)
             obj.author = self.request.user
             obj.save()
             form.save_m2m()
# This is my try. But it doesn't work as expected. 
             if followers.obj.author:
                    send_mail(
                        "Book is published",
                        "Book is published"
                        "host__email",
                        [followers_email],
                        fail_silently=False,
                    )
                    return redirect("/")
                else:
                    return redirect("/")
       return self.render_to_response({"form": form})

You have the author - either self.request.user or obj.author. From here forward, I’m going to refer to that author using the generic name author.

The set of Followers relating to that author is accessed through the manager named follower as defined in the related_name field. (That’s the set of all Followers whose follower field is referring to the author.)

So, author.follower.all() is a queryset of all Follower objects for author. Each element of that queryset can access the email address of that follower by following the FK links. e.g. If a_follower is an instance of author.follower.all(), then a_follower.following.email is a reference to their email address.

Having said all this, what you’ve really created here is a ManyToMany through table. This can be simplified somewhat by defining the ManyToMany relationship in CustomUser and taking advantage of the ManyToMany semantics available when writing queries.
See:

Ken, thank you again.
I tried to build this. But I have an error - “‘QuerySet’ object has no attribute ‘follower’”.

class BookCreateView(LoginRequiredMixin, CreateView):
    model = Book
    form_class = BookForm
    template_name = "book/example.html"
  
        def form_valid(self, form):
        form.instance.author = self.request.user
        return super(BookCreateView, self).form_valid(form)

    def post(self, request, *args, **kwargs):
        form = self.get_form()
        if form.is_valid():
             obj = form.save(commit=False)
             obj.author = self.request.user
             obj.save()
             form.save_m2m()
# This is my new try.
              follower = obj.author.following.all()
               follower_email = follower.follower.email

                    send_mail(
                        "Book is published",
                        "Book is published"
                        "host__email",
                        [follower_email],
                        fail_silently=False,
                    )
                    return redirect("/")
                else:
                    return redirect("/")
       return self.render_to_response({"form": form})

And thank you for this. In next time, I will use this method. It’s very close to what I want to build.

What is the data type of follower as a result of this statement? (Or, to phrase this same question another way, what class of object is returned by the statement obj.author.following.all()?)

1 Like

I think in that case, I get all cases where obj.author has the “following” statement.

Correct. More specifically, you get what Django refers to as a “queryset”.

A queryset containing a list of objects is not that object.

Therefore:

1 Like

Ken, thank you!
You always give me useful answers and descriptions of why this is so. My knowledge grows and improves by you. Thank you very much for this!

class BookCreateView(LoginRequiredMixin, CreateView):
    model = Book
    form_class = BookForm
    template_name = "book/example.html"
  
        def form_valid(self, form):
        form.instance.author = self.request.user
        return super(BookCreateView, self).form_valid(form)

    def post(self, request, *args, **kwargs):
        form = self.get_form()
        if form.is_valid():
             obj = form.save(commit=False)
             obj.author = self.request.user
             obj.save()
             form.save_m2m()
# It works
              if obj.author.following.all():
               user_obj = obj.author
               followers_list = Followers.objects.filter(
                        following=user_obj)

               for follower in followers_list:
                        send_mail(
                            "Article published",
                            'Article published "%s" by %s' % (
                                obj.title, obj.author),
                            "host_email",
                            [follower.follower.email],
                            fail_silently=False,
                        )
                    return redirect("/")
                else:
                    return redirect("/")
       return self.render_to_response({"form": form})