I have researched this and many answers out there touch on it (formsets, prefixes, etc), but none is exactly what I'm after...
I have an application that allows users to post cards to a board and each card can have zero or more comments. I have the database relationships set up so if I manually add board, card, or comment objects I can display them in the template just fine.
The card form is also working, so this is not an issue. But now I want to display a comment textarea with a click event (which I have working with jQuery). The problem is, the form is not displaying the field. Here is the form code:
class AddCommentForm(ModelForm):
class Meta:
model = Comment
fields = ['message']
message = forms.CharField(
widget=forms.Textarea(
attrs={
'class': 'form-control comment-input'
'name': 'message',
'placeholder': 'Comment away...'
}
),
required=False,
)
Right off the bat, there is a problem in that when Django renders a form, it uses an id. In this case, it ends up being id_message or message (depending on whether I specify in form class). Of course, in HTML, this is no good. I confirmed this in the shell:
>>> import django; import project
>>> from apps.board.forms import AddCommentForm
>>> comment_form = AddCommentForm(); print(comment_form)
<tr><th><label for="message">Message:</label></th><td><textarea class="form-control comment-input" cols="40" id="message" name="message" placeholder="Comment away..." rows="10">
</textarea></td></tr>
So I thought of using formsets but it seems not quite right, because as I understand them, they are for several forms inside one form tag. My case is different, in that each card has zero or more comments and the flow of the HTML dictates that there should be several form elements.
Another challenge is that I need to have two views: one to process the cards and one to process each comment. I'm currently using different action values to accomplish this and it seems to work.
It seems like my use case is not so strange, and I can think of several cases where this kind of situation would occur: Twitter replies, comments on blog posts, etc. I just can't get Django to work with this.
I should also note that I can manually make this work with this:
<label>
<textarea class="form-control comment-input" name="message"></textarea>
<button type="submit"></button>
</label>
UPDATE
Here is a view segment:
class AddCommentView(View):
model = Comment
form_class = AddCommentForm
template_name = 'somedir/template.html'
#method_decorator(login_required)
def get(self, request):
comment_form = self.form_class()
return render(request, self.template_name, {
'comment_form': comment_form
})
If the form is working (from snippet above and shell test), I would expect this to show it on the page.
The URLConf is here:
url(r'^comment/add/$', AddCommentView.as_view(), name='add_comment_view'),
In writing this out, I see a confusing piece. My URL for the view is /comment/add/ but I want the form to be rendered at /dashboard/. The problem is that there is another view at that URL already.
If I build the form without Django forms, then post data to /comment/add/ the comment is created and saved in the database. So I guess I'm still trying to figure out how to use two views in one template with multiple forms. #Daniel Roseman points out the use of prefix and I guess this is worth exploring more.
UPDATE 2
So I got this to work using prefixes by removing the URL for that view and combining the views into one view. Two forms, one view, one template. But...
The source still shows replicate ids. I ended up not putting the id in the form attribute list. Since this is invalid HTML, what is best way to get around this?
In my view:
card_model = Card
comment_model = Comment
card_form_class = AddCardForm
comment_form_class = AddCommentForm
. . .
#method_decorator(login_required)
def get(self, request):
card_form = self.card_form_class(prefix='card')
comment_form = self.comment_form_class(prefix='comment')
. . .
UPDATE 3
I have found a solution to this problem. To instantiate the form, you need to add auto_id=False to prevent multiple, replicate ids:
comment_form = self.comment_form_class(prefix='comment', auto_id=False)
You can use the prefix parameter when instantiating your forms to give them non-conflicting ids.
While using the Django form prefix was partially correct (thanks # Daniel Roseman), the assertion that it doesn't matter how many form elements I have is not quite correct. It does matter, because by default the prefix was meant to be used in one form (as it creates multiple replicate ids). To use it in multiple forms, you also need auto_id=False. This prevents the id from being generated by the Django form.
Related
In a Django 1.11 app I've created a large form for users to update a model instance, but based on what the users change there may be multiple other forms I'd like to redirect them to afterwards.
What's the best practise for dynamically chaining multiple forms (dynamic workflow)?
I can't find anything helpful in the Django docs or other questions on here.
E.g. (extremely simplified) view for model update form:
def asset_edit(request, pk):
asset = get_object_or_404(Asset, pk=pk)
current_location_concat = "{} {} {}".format(asset.location_building, asset.location_room, asset.location_area)
if request.method == "POST":
form = EditAsset(request.POST, request.FILES, instance=asset)
if form.is_valid():
asset = form.save(commit=False)
# setting some things...
asset.save()
new_location_concat = "{} {} {}".format(asset.location_building, asset.location_room, asset.location_area)
if current_location_concat != new_location_concat:
check_relatives = True # redirect to check_relatives form
if asset.equipment_id:
if Asset.objects.filter(equipment_id=asset.equipment_id).count() > 1:
duplicate_assets = True # redirect to check_duplicates form
# if check_relatives and duplicate_assets:
# redirect to check_duplicates form and then on to check_relatives form
return redirect("asset_detail", pk=asset.pk)
I know I could just add a new URL for my check_duplicates form with a "next" (or similar) parameter, pass a "next" value that the check_duplicate view maps to one or more other forms (just "check_relatives" in this instance) and redirects to this when the check_duplicates form is submitted, but is this the best practise?
Especially given that the number of forms that may need to be chained this way could get quite large and the logic complex!
I have been using formtool's WizardView for this and I have tried the 'stuff-everything-into-the-post-method' approach as well.
WizardView looks like the right fit for what you are trying to do.
For example, it allows you to skip steps in your workflow based on function conditions - removing this bit of logic from your workflow, which makes the whole view easier to read.
I have found it a pain to write tests for such views as you have to account for the WizardView's very own internal structure while testing, but overall it's definitely better than writing a 1000 lines post-method (might as well code in BASIC then).
I am building a website and I want various views that will ask the user to request a quote from our page. I want to keep the code as DRY as possible so I am writing a view quote which will receive the quote requests from various views and, if there is a validation error redirect back to the page that made the request. I managed to solve this using the super bad practice 'global variables'. I need a better solution, I would like redirecting to respective view with the current form so I can iterate through the form.errors. Here is my code:
def send_quote(request):
form = Quote(request.POST)
if form.is_valid():
# do stuff when valid
return redirect('Support:thanks', name=name or None)
quote_for = request.POST['for_what']
global session_form
session_form = form
return redirect('Main:' + quote_for) # Here I would like to send form instead of storing in global variable`
You can use the HttpResponseRedirect function, and pass as argument the page that made the request.
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
All the META data is store on a dictionary, if you want to learn more check the documentation.
https://docs.djangoproject.com/en/dev/ref/request-response/#django.http.HttpRequest.META
If you redirect to the referrer, form.errors will be empty, as redirection is always a GET request.
I can think of two solutions to your problem:
Submit forms asynchronously using JavaScript and so populate the errors
Make all the views containing the form support POST - one way to do this would be to create a base class that inherits from FormView
The second option is a typical way of handling forms in Django - you process both POST and GET inside the same view.
After two days of searching I finally found the answer. Instead of saving form in request.session I just save request.POST and then redirect. Here is the code:
def send_quote(request):
form = Quote(request.POST)
if form.is_valid():
# do stuff when valid
return redirect('Support:thanks', name=name or None)
quote_for = request.POST['for_what']
request.session['invalid_form'] = request.POST
return redirect('Main:endview')
def endview(request):
session_form = request.session.pop('invalid_form', False)
if session_form:
form = Quote(session_form)
# render template again with invalid form ;)
Now I can repeat this with all the views I want and just change the what_for input of each form to match the respective view (Like I intended).
I have a model named Book that I send to an html to display data from like this:
books = Book.objects.all()
return render(request, 'index.html', {'books':books})
I then show all the information I want of the book in the front-end (which doesn't need to be shown here).
Other than showing the information of the book in the front-end I also want to introduce a small form that will have two buttons let's say "Submit" and "Decline" that will update an attribute of the Book object depending on the button clicked.
I am trying to find a good way to achieve this. My thought is to POST the entire object to a view to process later but I am not sure if is a good practice to submit an entire object from client to server as this may be affected in some way.
This may help you :
If you want to update database with other values related with Book's object then you can achieve it by doing the following:
if request.POST:
book_form = BookForm(request.POST)
if book_form.is_valid():
book = Book.objects.get(pk=book_id)
book_form = BookForm(request.POST, instance = book)
book_form.save()
It will update the existing bookObject in Database by filtering on the basis of book_id.
You don't need to send any form data if it's just an attribute you'll update. You can do something like this (I'm using uuid just as an example you can use id or whatever unique field you want):
# url for your update view. For example: decline
url(r'^decline/book/(?P<book_uuid>[\w\-]+)$',
YourView.as_view(),
name="book-decline"),
# views.py
class YourView(View):
def get(self, request, *args, **kwargs):
book_uuid = kwargs.get('book_uuid', None)
book = get_object_or_404(Book, uuid= book_uuid)
# UPDATE YOUR BOOK HERE
# in your templates for each book's decline button link
<a href={% "book-decline" book.uuid %} ....>
Tiny Instance provided a generic solution. If you want a more specific solution for the case of updating an object of a model by getting a form filled from the user, then Django has something inbuilt which does this for you. FormView is what you are looking for.
See the official documentation here -> https://docs.djangoproject.com/en/1.9/ref/class-based-views/generic-editing/#formview
I have a template that creates a text entry field and a checkbox. When the checkbox is unchecked, the text field is disabled and cleared, when it's checked, it's enabled, and the user may or may not have typed in it. In my controller I need to distinguish between the 2
cases when the checkbox is unchecked, and the checkbox is checked but the text field is blank. I can get the value of the text field, but not of the checkbox. Is there some way to do this? I've googled this, and I see it's been asked a few times here, but noneof the solutions seem to work for me.
request.POST.get('my_checkbox_field')
P.S. In Django, they're called "views" not controllers.
UPDATE (based on comment)
I'm taking "controller" to mean "view" since Django doesn't have a concept of controllers and they're closest to views. If that's not the case, by all means correct me. Given that, all function-based views at the very least require a request parameter. If you're using class-based views, then request is simply stored on the view object, so you just need to modify it to self.request. I suggest you take some more time to thoroughly read the docs, as this is pretty much bare minimal understanding stuff that is well documented.
Are you looking for this?
def myview(request):
form = MyForm()
if request.method == 'POST':
form = MyForm(request.POST)
if form.is_valid():
checkbox = request.POST.get('my_checkbox', False) # will be True if checked
if checkbox:
# check textfield content
else:
# do something else
return render_to_response(template, kwvars, context_instance=RequestContext(request))
I have a purchase page, it can take an optional argument as a gift, if it is a gift, the view passes a gift form to the template and if not, a regular purchase form.
my old regular url, which redirects to two seperate views:
(r'^(?P<item>[-\w]+)/purchase/$', 'purchase_view'),
(r'^(?P<item>[-\w]+)/purchase/gift$', 'gift_view'),
and the views was like this:
def purchase_view(request,item):
....use purchase form
def gift_view(request,item):
....use giftform
It is a bad design indeed, as both views having are almost everything same but the forms used.
I have also thougt about using GET and giving gift as a GET param however it wasnt a good idea as I am using POST method for these pages, especially would cause issue after validation.
How can I make this a single url and a single view?
Thanks
urls.py
url(r'^(?P<item>[-\w]+)/purchase/$', 'purchase_view', name='purchase_view'),
url(r'^(?P<item>[-\w]+)/purchase/(?P<gift>gift)/$', 'purchase_view', name='gift_view'),
views.py
def purchase_view(request, item, gift=False):
if gift:
form = GiftForm
else:
form = PurchaseForm
...