In my app, I have a view showing an article and the list of related comments. An authorized user can leave a comment or several comments, and I've already realized this functionality. Where I got stuck is how to make it possible for a user to edit or delete their comments. The tricky point is all these actions (add, edit, delete) should be performed on the same page. What is the best approach for achieving this?
The model:
class Comment(models.Model):
comments = models.ForeignKey(NewsItem, on_delete=models.CASCADE, blank=True, null=True, related_name='comments')
text = models.TextField(blank = False)
author = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
create_date = models.DateTimeField(default=timezone.now)
The form:
class CommentForm(ModelForm):
class Meta:
model = Comment
fields = ['text',]
The view:
<...>
if request.method == 'POST':
form = CommentForm(request.POST)
if form.is_valid():
new_comment = form.save(commit=False)
new_comment.comments = NewsItem(id = newsitem_id)
new_comment.author = User(id = request.user.id)
new_comment.save()
return HttpResponseRedirect(reverse('news:newsitem_slug', args=(newsitem_id, slug)) + '#comment_{0}'.format(new_comment.id))
else:
form = CommentForm()
<...>
The template:
{% if user.is_authenticated %}
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" name="submit_button" value="Leave a comment">
</form>
{% endif %}
And this is the list of comments, where the form for editing should be placed:
<ul>
{% for comment in newsitem.get_active_comments %}
<li>{{ comment.text }}<br>
{{ comment.author }} </li>
{% if comment.author == user %}
The form for editing and the button for deleting should be here.
{% endif %}
{% endfor %}
</ul>
The page is supposed to look like this:
Caption
Text text text text text text
text text text text text text.
Comments:
Comment 1 by an authorised user
Edit | Delete
Comment 2
Comment 3 by an authorised user
Edit | Delete
Comment 4
Comment 5
Write a comment [Form]
You should manage this using ajax calls.
This is the most 'easy' way to manage this and stay in the same page.
Related
I have the form in my forms.py:
class NameForm(forms.Form):
your_name = forms.CharField(label='Your name', max_length=100)
age = forms.CharField(label='Age', max_length=100)
sex = forms.CharField(label='Sex', max_length=100)
I created the formset_factory
NameFormSet = formset_factory(NameForm, extra=0)
In my views.py for the get context_data, I have:
...
def get_context_data(self, **kwargs):
context = super(APView, self).get_context_data(**kwargs)
if self.request.POST:
context['formset'] = NameFormSet()
else:
recommended = returnWebAttackResults(self.kwargs['webAttack'])
if recommended is None:
context['recommendedAP'] = False
else:
context['formset'] = NameFormSet(initial=recommended[0])
return context
....
In my template file, I have:
<form class="row" method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ formset.management_form }}
{{ formset.non_form_errors }}
{% for forma in formset.forms %}
{{ forma }}
{% endfor %}
<input class="btn bg-success" type="submit" value="Update" />
</form>
My problem is that I don't get "This field is required" when I click the "Update" button. I tried setting the use_required_attribute to True, but it did not work.
Scenario:
The returnWebAttackResults function gets the initial data for the forms in the formset. So, if I have three forms, if one of the fields is blank (no user input) in any of the forms, then when I click the Update button, each field that is blank should be highlighted with the "This field is required". I can do this when I just render a regular form, but when I am using formsets, it does not work.
Is there a way to validate the forms in the formset before the request in sent?
I hit the same problem and found this in the documentation
https://docs.djangoproject.com/en/3.0/ref/forms/fields/#core-field-arguments
Widgets of required form fields have the required HTML attribute. Set the Form.use_required_attribute attribute to False to disable it. The required attribute isn’t included on forms of formsets because the browser validation may not be correct when adding and deleting formsets.
If you have an empty row (for adding an extra record) then you can't save the form until the empty row is filled in. So, if you want to say update one existing row without adding an extra row then you have a problem, you can't do it. The problem really comes from the fact that a formset is really in html terms just a single form. A workaround for that problem might be to use javascript to add rows to the formset only as needed, as in this example:
https://whoisnicoleharris.com/2015/01/06/implementing-django-formsets.html
There is probably furthermore a javascript solution to validate the form fields before you send the request, I think this may be such a solution but I haven't tried it:
https://jqueryvalidation.org/required-method/
It would make sense that a completely empty row should not be validated for required fields but just ignored altogether.
UPDATE: I got a simple solution working from the example in the above link. My template looks like this (note that no empty row is shown initially thanks to the if statement):
{% extends "base_generic.html" %}
{% load static %}
{% block content %}
<h1>Experiment Detail</h1>
<form name="test_form" method="post">
{% csrf_token %}
{{ formset.management_form }}
{% for form in formset %}
{% if form.name.value %}
<div class="link-formset">
{{ form.name }}
</div>
{% endif %}
{% endfor %}
<input type="submit" value="Save" class="button"/>
</form>
<!-- Include formset plugin - including jQuery dependency -->
<!-- <script src="{% static 'path_to/jquery-3.4.1.js' %}"></script> -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="{% static 'js/jquery_formset.js' %}"></script>
<script>
$('.link-formset').formset({
addText: 'Add',
deleteText: 'Delete'
});
</script>
{% endblock %}
In my view I have this for loop to set the use_required_attribute:
for form in formSet:
form.use_required_attribute = True
The solution seems to work OK, when I click the Add button an empty row appears and the empty 'name' field has the expected red box round the empty field and when I try to click Save I get the message 'Please fill out this field.'
the solution is, add BaseFormSet in froms.py, like this:
forms.py
from django.form import BaseFormSet
class NameForm(forms.Form):
your_name = forms.CharField(label='Your name', max_length=100, blank=False)
age = forms.CharField(label='Age', max_length=100, blank=False)
sex = forms.CharField(label='Sex', max_length=100, blank=False)
class RequiredFormSet(BaseFormSet):
def __init__(self, *args, **kwargs):
super(RequiredFormSet, self).__init__(*args, **kwargs)
for form in self.forms:
form.empty_permitted = False
form.use_required_attribute = True
NameFormSet = formset_factory(NameForm, formset=RequiredFormSet)
Several DEVICE forms can be displayed on a page (Order), as tabs. When a user closes a tab and hits save, then renters that particular Order (ID), the user still sees his deleted tab. As a quick workaround, a checkbox was built into the html code but this is not desired.
Currently the code in my view.py looks like this.
def edit_order(request,id = None, order_id = None):
order = Order.objects.get(id=id)
'''some stuff'''
if request.method == 'POST':
formCustomer = CustomerModelForm(request.POST,instance=Customer.objects.get(order=order.id))
formInfo = InfoModelForm(request.POST,instance=Info.objects.get(order=order.id))
DBFormSet = modelformset_factory(DB, extra=1, can_delete=True, form=LUNModelForm)
formset = DBFormSet(request.POST or None, queryset=DB)
if formset.is_valid():
for i,frm in enumerate(formset.forms):
if frm.cleaned_data['id']:
if frm.is_valid():
if not frm.cleaned_data['capacityGB'] > 0:
frm.cleaned_data['id'].delete()
elif frm.cleaned_data['DELETE'] == True:
frm.cleaned_data['id'].delete()
#frm.save() or formset.save() ??
else:
dev = frm.cleaned_data['id']
dev.capacity = frm.cleaned_data['capacity']
dev.save()
I suspected the matter that the tab was not remaining closed was an issue with "can_delete", but it doesn't seem to make a difference when I added this. Then I read that the issue could be related to the formset.save, which also didn't resolve anything. Below the html code:
html:
<ul class="nav nav-tabs" id="tab4Headers">
{% for form in formsetDEVICE.forms %}
{% if forloop.first %}
{% if form.errors %}
<li class="active">DEVICE <i class="icon-warning-sign"></i> </li> <!-- section 4.1 -->
{% else %}
<li class="active">DEVICE</li> <!-- section 4.1 -->
{% endif %}
{% else %}
{% if form.errors %}
<li><button class="close" type="button" id="close-tab4_{{forloop.counter}}">×</button>DEVICE <i class="icon-warning-sign"></i> </i></li> <!-- section 4.2 ... 4.n -->
<!--{% if formsetDEVICE.can_delete %}
<li>{{ form.DELETE }}</li>
{% endif %}-->
{% else %}
<li><button class="close" type="button" id="close-tab4_{{forloop.counter}}">×</button>DEVICE</li> <!-- section 4.2 ... 4.n -->
<!--{% if formsetDEVICE.can_delete %}
<li>{{ form.DELETE }}</li>
{% endif %}-->
{% endif %}
{% endif %}
{% endfor %}
</ul>
Since it is not clear how to delete the tab via closing the tab (and then hit the save button), the current work around is simply to insert a section on the tab with a checkbox included, as so:
{# --- delete item --- #}
{% if form.instance.pk %}
{# render row for DELETE-flag only for forms with existing instance #}
<tr>
<td colspan="2">
</td>
<td colspan="2">
edit Action required: <br/><br/>
{{form.DELETE|safe}} delete this DEVICE from the Order.
</td>
</tr>
{% endif %}
{# --- END delete item --- #}
The current workaround works, but this is not the desired functionality. I've tried some of the suggestions as seen here, but to no avail. I think the problem is more related to the link between the html and the view.py (so something to do with frm.cleaned_data['DELETE'] though I could be wrong.
EDIT:
I suspect to get around this, js code and/or css needs to be inserted.
I was having the same problem with inlineformset_factory. Reading the base Django classes, i found the problem seems to be in BaseFormSet class (or some like that), the _should_delete_form() method looks for the forms DELETE value in cleaned_data, but using debugging i found that the DELETE value doesn't exist in cleaned_data, neither the DELETE field exists by the time cleaned_data is populated.
I think this could be because the DELETE field is dynamically created by FormSet Classes, but cleaned_data is populated by the Form class before the DELETE field is created (somewhere on forms init? maybe) so as this field doesn't exists, cleaned_data DELETE value isn't populated.
Somewhere in the base FormSet classes, the dynamically created DELETE field value have to be included in cleaned_data, BUT i found a quick and temporary solution to this, Add the DELETE field to the form class:
class RutinaForm(ModelForm):
DELETE = forms.BooleanField(required=False, widget=forms.CheckboxInput())
class Meta:
model = Rutina
fields = '__all__'
exclude = ('usuario',)
widgets = { .........}
This way the DELETE field exists so its value gets cleaned and added to cleaned_data.
This can be seen like a "machetazo", but it works! at least temporary. I will continue reading the base code and look if it is a bug, or what is happening.
Note: Sorry for my english, and i'm not an expert so PLEASE give me your comments.
This is my view if you need it, or if you can find what is wrong:
rutinaFormSet = inlineformset_factory(Usuario, Rutina, form=RutinaForm, can_delete=True, extra=1)
if request.method == 'POST':
formSet = rutinaFormSet(request.POST, instance=usuario)
if formSet.is_valid():
formSet.save()
else:
formSet = rutinaFormSet(instance=usuario)
Hi I have a 'member' model that references a 'role' model. What I would like is when a user creates a 'member' they can either choose from a list of default 'roles' (that have been loaded to the database when the server starts using fixtures) or they can click on add role and then type in and click submit and the newly created role is created and assigned to the newly created 'member' object.
I would like it so that I am able to just create a role without submitting, and the role then appears in the dropdown menu for the role field in the member model. Is this where I would need to learn about AJAX to implement this feature?
Models
class MemberRole(models.Model,get_fields):
name = models.CharField(max_length = 20)
def __unicode__(self):
return self.name
class Member(models.Model,get_fields):
first_name = models.CharField(max_length = 20)
role = models.ForeignKey(MemberRole, null = True, blank = True)
View
def add_member(request):
model_url = 'member-add'
if request.method == "POST":
rform = MemberRoleForm(request.POST, instance=MemberRole())
mform = MemberForm(request.POST, instance=Member())
if rform.is_valid() and mform.is_valid():
new_role = rform.save()
new_member = mform.save(commit=False)
new_member.role = new_role
new_member.save()
return HttpResponseRedirect('members')
else:
rform = MemberRoleForm(instance=MemberRole())
mform = MemberForm(instance=Member())
return render_to_response('create_model.html', {'role_form': rform, 'member_form': mform, 'model_url': model_url,},context_instance=RequestContext(request))
snippet create_model.html
<div id = "subtemplate">
<form action="{% url model_url %}" method="POST">
{% csrf_token %}
{% if model_url == 'member-add' %}
{% for field in member_form %}
{% if field.label == 'Role' %}
<div id="roleExistsMemberForm">
<button type="button" onclick="showDiv()">Add Role</button>
{{ field.errors }}
{{ field.label_tag }} {{ field }} {{ field.help_text }}
</div>
{% else %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
</div>
{% endif %}
{% endfor %}
<div Id="addRoleOnMemberForm">
{% for field in role_form %}
{{ field.errors }}
{{ field.label_tag }} {{ field }}
{% endfor %}
</div>
{% else %}
<ul>
<li>{{ form.as_ul }}</li>
</ul>
{% endif %}
<div id="centerbuttons">
<input id="save_contact" type="submit" value="Add"/>
</div>
</form>
Yes, Ajax would be the first point of call for something like this. You can submit the form in the background to a Django view, the view could then respond with a fresh list of options to re-populate the drop down list with.
I have an initial form as below
forms.py
class LoginForm(forms.Form):
activity_no = forms.CharField()
username = forms.CharField()
views.py
def test(request):
if request.method == 'POST' :
form = LoginForm(request.POST)
if form.is_valid() :
act_no = form.cleaned_data['activity_no']
username = form.cleaned_data['username']
form_data = {}
form_data['activity_no'] = act_no
form_data['username'] = username
return render_to_response("test1.html", { 'form_data' : form_data}, context_instance=RequestContext(request))
else:
form = LoginForm()
return render_to_response("test.html", {'form': form }, context_instance=RequestContext(request))
test.html
{% extends "admin/base.html" %}
{% block content %}
<form action="/todo/test/" method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Submit" />
</form>
{% endblock %}
test1.html
{% extends "admin/base.html" %}
{% block content %}
<form action="/todo/login/" method="post">{% csrf_token %}
{% for name, data in form_data.items %}
<input type="hidden" name={{name}} value={{data}}>
{% endfor %}
<input type="submit" value="Yes"/>
</form>
{% endblock %}
I fill this form, display few contents of a file and pass the above form data to a form wizard
But when I pass the data to the form wizard via post method in the template, I get a "ManagementForm Data missing" error but if the data is passed through the GET method, I don't get any error but as defined, the data is seen in the url in the GET method ( in my case it contains username which I don't want to disclose)
My Form Wizard
class LoginWizard(SessionWizardView):
def __name__(self):
"""When using decorators, Django tries to get the name of the
function and since we're a class, we'll fail. So add this method to
compensate."""
return 'LoginWizard'
template_name = "wizard_form.html"
def done(self, form_list, **kwargs) :
form_data = process_form_data(form_list)
My query is that how would I handle the post data in the form wizard.
Please let me know if I am missing anything or any other information is required from my end.
You need to add {{ wizard.management_form }} in your template as explained it in reference django wizard templates.
Like:
{% extends "admin/base.html" %}
{% block content %}
<form action="/todo/test/" method="post">{% csrf_token %}
{{ wizard.management_form }}
{{ form.as_p }}
<input type="submit" value="Submit" />
</form>
{% endblock %}
Add that in all templates for wizard.
The following is my "view" which renders a form through "editpost.html". I want to update the timestamp (datetime field) to be edited by the system time, but don't want that to be visible to the user. I tried using "hidden" keyword in the modelform definition Meta class, but that does not update the time correctly. I tried doing it in the template but doesn't work. Any ideas?
def editpost(request, postid):
blog = BlogPost.objects.get(pk=postid)
if request.method == 'POST':
form = BlogPostForm(request.POST, instance=blog)
blog = form.save()
return HttpResponseRedirect('/blog/%s' % str(postid))
else:
form = BlogPostForm(initial={'timestamp': datetime.now()}, instance=blog)
form.save(commit=False)
return render_to_response("editpost.html", {'form': form, 'postid': postid}, RequestContext(request))
editpost.html
{% block content %}
<form action="." method="post">{% csrf_token %}
{% for field in form %}
{% if field.label_tag != "Timestamp" %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
</div>
{% endif %}
{% endfor %}
<input type="submit" value="Save" />
</form>
{% endblock %}
May be better would be rewrite def save(...) of the model. You can try it:
def save(self, *args, **kwargs):
self.timestamp = datetime.now()
super(Dog,self).save(*args, **kwargs)