Sending POST data from inside a Django template 'for loop' - python

With this HTML:
...
{% for thing in things %}
<form method="post">
{% csrf_token %}
{{ thing.name }}
{{ form.value }}
<input type="submit" value="Submit" />
</form>
{% endfor %}
...
My website lists multiple 'things' from my database, so there can be many forms generated on the one page. How can I somehow determine in my views.py, which 'thing's' form is being submitted?
More elaboration:
Imagine you have a page of objects listed one after the other, and each object has a like button associated with it, that adds a like to the object it is next to. That's essentially what I'm trying to do here.
The problem is, I have a form that can process the like, but how do I take that like and add it to the object that it's displayed next to on the page? (by the aforementioned 'for loop')
I'm completely confused on how to go about this, am I looking at the problem the wrong way, or is there a standard idiom around this problem that I don't know about?
Thank you :)

The most common design pattern for model instance updates is to provide the primary key of an object in the url where you are submitting your post data.
# urls.py
from django.conf.urls import *
from library.views import UpdateThing
urlpatterns = patterns('',
url('^update_thing/(?P<pk>[\w-]+)$', UpdateThing.as_view(), name='update_thing'),
# views.py
def my_view(request, pk=None):
if pk:
object = thing.objects.get(pk=pk)
form = MyModelForm(data=request.POST or None, instance=object)
if form.is_valid():
...
Now, let's specify (using Django's url template tag) that we want to submit post data for each object to the correct url.
{% for thing in things %}
<form method="post" action="{% url 'update_thing' thing.pk %}">
{% csrf_token %}
{{ thing.name }}
{{ form.value }}
<input type="submit" value="Submit" />
</form>
{% endfor %}
The url tag does a reverse lookup through your urls for the name kwarg supplied for a given url, and accepting positional arguments (such as, in this case, thing.pk) and, when needed, keyword arguments.

The standard way to handle multiple forms of the same kind on one page with Django is to use Formsets.
It handles the annoying details like displaying errors on one form while preserving the input on others etc.
However, in your specific case that might be overkill. If you just want to create a like for an object, there isn't really any user input that needs to be validated, so you don't really need a form. Just perform a POST to a specified URL, maybe with Javascript. If the user messes with the URL, you display a 404.

Related

How to Prefill Form with URL Parameters (Django)

I would like to prefill a form with URL parameters, but I am unsure as to how I should configure my URLs. I need to fill multiple fields, so is using URL parameters still the best method? In the tutorials I have been reviewing, most cases only use 1 or 2 parameters from the GET request. In my view, I am only handling one field currently as I am having trouble with just one parameter. You can see in the form model the other fields I would like to fill. Any help is greatly appreciated!
views.py
def new_opportunity_confirm(request):
form_class = OpportunityForm
account_manager = request.GET.get('account_manager')
form = form_class(initial={'account_manager': account_manager})
return render(request, 'website/new_opportunity_confirm.html', {'form': form})
urls.py
re_path(r'new_opportunity/new_opportunity_confirm/(?P<account_manager>\w+)/$', view=views.new_opportunity_confirm,
name='new_opportunity_confirm'),
new_opportunity_confirm.html
<form action="" method="post" name="newOpportunityForm" id="newOpportunityForm">
{% csrf_token %}
<div class="field">
<label class="label">Account Manager:</label>
<div class="select">
<select name="account_manager" id="account_manager" required>
<option value="{{ form }}">{{ form }}</option>
</select>
</div>
</div>
It depend if you want your parameters to be part of the url or not, and in your case I would suggest not, but let's see both method.
For GET parameters (url?var1=poney&var2=unicorn):
You do not need to configure your url. Django will do the work for you, you just have to configure what is before the interrogation point.
You can then access those with request.GET.get("var1"), or request.GET.get("var1", "default") if you want a default value in case it's not found.
In your template, you can access it with {{ request.GET.var1 }}.
For parameters in the url (url/poney/unicorn):
You need to configure the url to capture the part you want, and you need to have a parameter in the receiving view to get the one in the URL:
def new_opportunity_confirm(request, account_manager):
You can then access it like any other variable, and send it to your template if you want to have access to it there.
Again, that second way does not seem fitting to what you want to achieve.
You were halfway there, you just mixed a little bit of both methods.

Django not passing data to views on POST request

I've created a drop-down menu that is supposed to pass data to a view that'll help filter a queryset. However, it doesn't seem like the data is actually being passed to the view. Below is the relevant code I've written.
template.html
<!-- Query based content for dropdown menu -->
<form method="POST" action="{% url 'property-selected' %}" id="property-select">
{% csrf_token %}
<select class="dropdown-content" onchange="this.form.submit()" name="property-select">
{% if current_user_meters %}
<option disabled selected> -- select an option -- </option>
{% for meter in current_user_meters %}
<option class="dropdown-menu-option" value="{{meter.id}}">{{meter.name}}</option>
{% endfor %}
{% else %}
<option>You don't have any meters</option>
{% endif %}
</select>
</form>
views.py
def property_selected(request):
if request.method == 'POST':
selection = request.POST.get('property-select')
current_user_groups = Group.objects.filter(
id__in=request.user.groups.all()
)
current_user_properties = Property.objects.filter(
groups__in=current_user_groups
)
current_user_meters = Meter.objects.filter(
meter_id__in=current_user_properties
)
selected_meters = Meter.objects.filter(name=selection)
selected_meter_data = MeterData.objects.filter(
name=selection
).order_by('date')
return render(request, 'properties/property-selected.html', {
'current_user_meters': current_user_meters,
'selection': selection,
'selectected_meters': selected_meters,
'selected_meter_data': selected_meter_data,
})
For the querysets in the views file, the selection variable doesn't seem to be getting anything, which is where I want the data from the POST request to go. I want the data from the POST request to go there so my selected_meters and selected_meter_data queries will work as intended.
The values in the property-select options are IDs, but you are trying to filter MeterData by name with those values. Either filter by id, or use the name attribute as the option values.
You need to either use a form to ingest your data (thereby providing cleaning and validation) or access request.body instead of request.post, and then parse it for yourself.
From the documentation, emphasis mine (https://docs.djangoproject.com/en/dev/ref/request-response/#django.http.HttpRequest.POST):
HttpRequest.POST¶ A dictionary-like object containing all given HTTP
POST parameters, providing that the request contains form data. See
the QueryDict documentation below. If you need to access raw or
non-form data posted in the request, access this through the
HttpRequest.body attribute instead.
It’s possible that a request can come in via POST with an empty POST
dictionary – if, say, a form is requested via the POST HTTP method but
does not include form data. Therefore, you shouldn’t use if
request.POST to check for use of the POST method; instead, use if
request.method == "POST" (see HttpRequest.method).
I'd recommend passing the request into a form. It gives you some nice functionality down the line, and lets you avoid parsing request.body for yourself, even if it's an extra step.

Why is my flask form validation returning Not a valid choice?

I have been trying to figure out why my Flask form will not properly validate my select field choices even though the choices are coming from the select field options.
My assumption is that the select option when passed back from the server is unicode and is being compared to the choice which is a string, however, I thought coerce=str would fix that. I printed out the form data and request data which is the output below. Why isn't it working?
My code is attached below, removed csrf token key from the output dict. It seems like a very simple thing, but I can't figure it out.
forms.py
class PlatformForm(FlaskForm):
platform_options = [('test', 'Test'), ('test2','Test2')]
platforms = wtforms.SelectField('Platforms', choices=platform_options, coerce=str, validators=[DataRequired()])
views.py
#app.route('/', methods=['POST', 'GET'])
def index():
form = forms.PlatformForm()
if form.is_submitted():
print form.data
print request.form
if form.errors:
print form.errors
return render_template('home.html', form=form)
index.html
{% extends "base.html" %}
{% block content %}
<h4>Select a Platform</h4>
<form method="POST">
{{ form.csrf_token }}
<select class="custom-select" name="platform">
{% for value, text in form.platforms.choices %}<br>
<option value="{{ value }}">{{ text }}</option>
{% endfor %}
</select>
<button id="submit_inputs" type="submit" class="btn btn-default">Submit</button>
</form>
{% endblock %}
output
{'platforms': 'None'}
ImmutableMultiDict([('platform', u'test')])
{'platforms': [u'Not a valid choice']}
EDIT:
I figured out the problem. It's the way I'm creating the Select drop down through HTML and Jinja. Iterating through the choices and creating option tags doesn't seem to instantiate anything in the form data itself when passed back into Python. Changing that whole for loop to just
{{form.platforms}}
created a select drop down field that actually works.
You have a name mismatch. In the form, you named your select field platforms (plural). In the HTML, you use platform (singular).
I recommend that instead of manually rendering the fields in your template, you let WTForms generate the HTML for you. For the form label, you can use {{ form.platforms.label }}, and for the actual field {{ form.platforms() }}. You can pass any attributes you want to field to have as keyword arguments.
I think something might be going wrong because of the way you are rendering the form in your html file. If my hunch is right, try this:
{% extends "base.html" %}
{% block content %}
<h4>Select a Platform</h4>
<form method="POST">
{{ form.hidden_tag() }}
Select: {{ form.plaforms}}
{{ form.submit(class="btn btn-default") }}
</form>
{% endblock %}
and then try if form.validate_on_submit() in your views.py file
taken from this stack overflow answer by pjcunningham:
"validate_on_submit() is a shortcut for is_submitted() and validate().
From the source code, line 89, is_submitted() returns True if the form
submitted is an active request and the method is POST, PUT, PATCH, or
DELETE.
Generally speaking, it is used when a route can accept both GET and
POST methods and you want to validate only on a POST request."

what is the best way to use less Django template syntax as possible with Django Form?

I am just beginner of Django.
At first, It seemed very convenient to use Django Form. I can get all which i already I defined in Model with ModelForm class. I can just put { form } in template. There are already pre-defined Field type and I can use it conveniently.
But it is tricky if you need to work with one who doesn't know Django or Python and are charge of making front-end part.
For example, I make template like this.
example.html
<form action="." method="post"> {% csrf_token %}
{{ form }}
<input type="submit" value="ok">
views.py
class TestView(FormView):
template_name = 'example.html'
form_class = TestForm
success_url = '/success/'
def form_valid(self, form):
print('data is validated! you can do work with cleaned data!')
return super(TestView, self).form_valid(form)
Q1 : A front-developer can't work with {{ form }}. right?
So I can change just example.html because it works properly if you set name attribute and type attribute correctly.
example.html
<form action="." method="post"> {% csrf_token %}
<input type="text" name="name">
<input type="text" name="calorie">
<input type="submit" value="Ok">
</form>
models.py
class Cereal(models.Model):
name = models.CharField(max_length=50)
calorie = models.CharField(max_length=50)
Q2 : I think it would be fine for a front developer to work with it if I change example.html above? right?
Well, if my questions are all fine, It seems fine to use ModelForm and write html code instead of {{ form }} django template syntax. But if you use ModelChoiceField, it seems nasty. You set queryset argument and it shows with select widget on template. for example queryset reqsult is { A, B, C }. it will turn to like below
<select id="id_something" name="name of something">
<option>A</option>
<option>B</option>
<option>C</option>
</select>
As i told you earlier, I don't want to use Django template syntax like {{ form }} as much as I can. so I am going to write like below instead.
views.py
render(request, "example.html", {queryset : something.objects.all()})
example.html
<select id="id_something" name="name of something">
{% for element in queryset %}
<option>{{ element }}</option>
{% endfor %}
</select>
Q3: does this approach seem alright? Isn't it better way to use less Django template syntax when you use Django Form?
well, I concluded that using DRF(Django RestFramework) is the best way to separate tasks for front-end and back-end developers independently. But In current situation I am facing, Template(html, css, js) is already made. I need to change it to use with Django framework. Front end developer will change little bit with the Template which is already made.
When I watched some pycon video in Youtube, some people said that Djang framework is kind of too much coupled.
I hope I can get Idea more efficiently to use Django Model form with front-end developer.
Thanks for reading. My question could be ridiculous for an expert. Please ask me again if it is not clear.

Add a custom button to a Django application's admin page

I have an application in Django with a routine which would be available only to the admin. What I want to do is add a button to perform the routine in this application's section of the admin app.
Am I supposed to make a template for it, and if that's the case, how do I add a html template for an app in the admin. Or maybe there's a command to simply add a button?
Messing with the admin forms can be complicated but i've commonly found that adding links, buttons, or extra info is easy and helpful. (Like a list of links to related objects witout making an inline, esp for things that are more viewed than edited).
From Django docs
Because of the modular design of the admin templates, it is usually
neither necessary nor advisable to
replace an entire template. It is
almost always better to override only
the section of the template which you
need to change.
This will add a list over the top of the form.
Place in templates/admin/[your_app]/[template_to_override]:
{% extends "admin/change_form.html" %}
{% block form_top %}
{% for item in original.items %}
{{ item }}
{% endfor %}
{% endblock %}
Django1.10:
1) Override admin/submit_line.html:
{% load i18n admin_urls %}
<div class="submit-row">
{% if extra_buttons %}
{% for button in extra_buttons %}
{{ button }}
{% endfor %}
{% endif %}
{% if show_save %}<input type="submit" value="{% trans 'Save' %}" class="default" name="_save" />{% endif %}
{% if show_delete_link %}
{% url opts|admin_urlname:'delete' original.pk|admin_urlquote as delete_url %}
<p class="deletelink-box">{% trans "Delete" %}</p>
{% endif %}
{% if show_save_as_new %}<input type="submit" value="{% trans 'Save as new' %}" name="_saveasnew" />{% endif %}
{% if show_save_and_add_another %}<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother" />{% endif %}
{% if show_save_and_continue %}<input type="submit" value="{% trans 'Save and continue editing' %}" name="_continue" />{% endif %}
</div>
This assumes, of course, that button's string representation is an appropriate browser input or button element, and is marked safe with django.utils.safestring.mark_safe. Alternatively, you could use the safe template filter or access the attributes of button directly to construct the <input>. In my opinion, it's better to isolate such things to the python level.
2) Override MyModelAdmin.change_view:
def change_view(self, request, object_id, form_url='', extra_context=None):
extra_context = extra_context or self.extra_context()
return super(PollAdmin, self).change_view(
request, object_id, form_url, extra_context=extra_context,
)
This method enables you to add buttons to any ModelAdmin easily. Alternatively to step (1), you could extend admin/change_form.html and override block submit_row. This would be slightly more verbose due to extra tags required in the template.
If you want the extra action available across all of your models (or a specific subset) then subclass ModelAdmin with the desired functionality (an example would be to add archiving to your models. You could even add an override for delete--and the other default buttons--so that the mode is archived instead of deleted; this would require some template modifications)
You can also use django-admin-tools, which allows you to easily customize the admin front page like a dashboard. Using a LinkList, you can point to some view method and check if the user is authenticated. It goes like thies:
# dashboard.py (read more about how to create one on django-admin-tools docs)
class CustomIndexDashboard(Dashboard):
"""
Custom index dashboard for captr.
"""
def init_with_context(self, context):
self.children.append(modules.LinkList(
_('Tasks'),
children=[
['Your task name', '/task']
]
))
# urls.py (mapping uri to your view function)
urlpatterns += patterns('yourapp.views',
(r'^task$', 'task'),
)
# views.py
def task(request):
if request.user.is_authenticated():
update_definitions_task.delay() # do your thing here. in my case I'm using django-celery for messaging
return redirect('/admin')
You might consider adding a custom admin action for this kind of object (similar to the built in 'delete'), if appropriate. Some benefits include: "pure Django", not having to mess with templates, and being able to act on multiple objects at once.
Django’s admin lets you write and register “actions” – simple
functions that get called with a list of objects selected on the
change list page. If you look at any change list in the admin, you’ll
see this feature in action; Django ships with a “delete selected
objects” action available to all models.
https://docs.djangoproject.com/en/dev/ref/contrib/admin/actions/
I got the idea from this article on how to add a custom action button, which is another answer all together. I was able to get by with the simpler built-in actions though.
https://medium.com/#hakibenita/how-to-add-custom-action-buttons-to-django-admin-8d266f5b0d41
Don't mess with the admin pages.
Create an "application" for this. Yes, your function is just a "routine". That's okay. Many smaller applications are a good thing.
This application has nothing new in models.py. No new model. Zero lines of code.
This application has a useful URL in urls.py. Something that can be used to display this admin page. One URL. Not many lines of code (less than a dozen.)
This application has one view function in views.py. On "GET", this view function presents the form. On "POST", this view function does the "routine". This is the "heart" of your application. The GET -- of course -- simply returns the template for display. The POST does the real work, and returns a final status or something.
This view function is protected with a decorator so that only an admin can execute it.
See http://docs.djangoproject.com/en/1.2/topics/auth/#django.contrib.auth.decorators.user_passes_test. You want to write a test for being an admin. lambda u: u.is_staff is probably it.
This application has one template, presented by the GET and POST. That template has your form with your button. The one you can't add to admin easily.
The tests.py is a test case with two users, one who is an admin and one who is not an admin.
No messing with built-in admin pages.

Categories