In a Django social networking website I built, users can chat in a general room, or create private groups.
Each user has a main dashboard where all the conversations they're a part of appear together, stacked over one another (paginated by 20 objects). I call this the unseen activity page. Every unseen conversation on this page has a text box a user can directly type a reply into. Such replies are submitted via a POST request inside a <form>.
The action attribute of each <form> points to different urls, depending on which type of reply was submitted (e.g. home_comment, or group_reply). This is because they have different validation and processing requirements, etc. 
The problem is this: If a ValidationError is raised (e.g. the user typed a reply with forbidden characters), it gets displayed on multiple forms in the unseen_activity page, instead of just the particular form it was generated from. How can I ensure all ValidationErrors solely appear over the form they originated from? An illustrative example would be great!
The form class attached to all this is called UnseenActivityForm, and is defined as such:
class UnseenActivityForm(forms.Form):
    comment = forms.CharField(max_length=250)
    group_reply = forms.CharField(max_length=500)
    class Meta:
        fields = ("comment", "group_reply", )
    def __init__(self,*args,**kwargs):
        self.request = kwargs.pop('request', None)
        super(UnseenActivityForm, self).__init__(*args, **kwargs)
    def clean_comment(self):
        # perform some validation checks
        return comment
    def clean_group_reply(self):
        # perform some validation checks
        return group_reply
The template looks like so:
{% for unseen_obj in object_list %}
    {% if unseen_obj.type == '1' %}
    {% if form.comment.errors %}{{ form.comment.errors.0 }}{% endif %}
    <form method="POST" action="{% url 'process_comment' pk %}">
    {% csrf_token %}
    {{ form.comment }}
    <button type="submit">Submit</button>
    </form>
    {% if unseen_obj.type == '2' %}
    {% if form.group_reply.errors %}{{ form.group_reply.errors.0 }}{% endif %}
    <form method="POST" action="{% url 'process_group_reply' pk %}">
    {% csrf_token %}
    {{ form.group_reply }}
    <button type="submit">Submit</button>
    </form>
    {% endif %}
{% endfor %}
And now for the views. I don't process everything in a single one. One function takes care of generating the content for the GET request, and others take care handling POST data processing. Here goes:
def unseen_activity(request, slug=None, *args, **kwargs):
        form = UnseenActivityForm()
        notifications = retrieve_unseen_notifications(request.user.id)
        page_num = request.GET.get('page', '1')
        page_obj = get_page_obj(page_num, notifications, ITEMS_PER_PAGE)
        if page_obj.object_list:
            oblist = retrieve_unseen_activity(page_obj.object_list)
        else:
            oblist = []
        context = {'object_list': oblist, 'form':form, 'page':page_obj,'nickname':request.user.username}
        return render(request, 'user_unseen_activity.html', context)
def unseen_reply(request, pk=None, *args, **kwargs):
        if request.method == 'POST':
            form = UnseenActivityForm(request.POST,request=request)
            if form.is_valid():
                # process cleaned data
            else:
                notifications = retrieve_unseen_notifications(request.user.id)
                page_num = request.GET.get('page', '1')
                page_obj = get_page_obj(page_num, notifications, ITEMS_PER_PAGE)
                if page_obj.object_list:
                    oblist = retrieve_unseen_activity(page_obj.object_list)
                else:
                    oblist = []
                context = {'object_list': oblist, 'form':form, 'page':page_obj,'nickname':request.user.username}
                return render(request, 'user_unseen_activity.html', context)
def unseen_group_reply(group_reply, pk=None, *args, **kwargs):
            #similar processing as unseen_reply
Note: the code is a simplified version of my actual code. Ask for more details in case you need them.
 
     
    