Reputation: 15844
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.
Upvotes: 1
Views: 612
Reputation: 19861
Following the discussion in the comments above:
What I suggest is that you create a form for each instance in the view. I have refactored your code to have a function which returns object lists which you can use in both unseen_reply
and group_reply
functions:
def get_object_list_and_forms(request):
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 = []
# here create a forms dict which holds form for each object
forms = {}
for obj in oblist:
forms[obj.pk] = UnseenActivityForm()
return page_obj, oblist, forms
def unseen_activity(request, slug=None, *args, **kwargs):
page_obj, oblist, forms = get_object_list_and_forms(request)
context = {
'object_list': oblist,
'forms':forms,
'page':page_obj,
'nickname':request.user.username
}
return render(request, 'user_unseen_activity.html', context)
Now, you need to access the form in template using the object id from forms
dict.
{% for unseen_obj in object_list %}
<!-- use the template tag in the linked post to get the form using obj pk -->
{% with forms|get_item:unseen_obj.pk as form %}
{% 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>
{% elif 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 %}
{% endwith %}
{% endfor %}
While processing the reply, you again need to attach the form which throws error with the particular object pk:
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:
page_obj, oblist, forms = get_object_list_and_forms(request)
# explicitly set the form which threw error for this pk
forms[pk] = form
context = {
'object_list': oblist,
'forms':forms,
'page':page_obj,
'nickname':request.user.username
}
return render(request, 'user_unseen_activity.html', context)
Upvotes: 1