Unable to pass multiple POST parameters from Django template - python

I'm passing POST parameters via a Django template. The template is basic:
<form method="POST" action="{% url 'registration_id' %}">
{% csrf_token %}
<input name="registration_id" value="{{ registration_id }}">
<input name="device_id" value="{{ device_id }}">
<button>Submit</button>
</form>
In the model, registration_id is a required TextField, whereas device_id is an optional hex field. To test my set up, I use a Mozilla add-on called HttpRequester. It works perfectly if:
1- I POST content to my URL like so:
csrfmiddlewaretoken=foobar&registration_id=foo
2- or like so:
csrfmiddlewaretoken=foobar&registration_id=foo&device_id=
(i.e. pass nothing in device_id, but mention the variable)
3- But nothing gets POSTed if I do:
csrfmiddlewaretoken=foobar&registration_id=foo&device_id=1234abcd
(ensuring device_id is a hex value)
Views.py is:
class DeviceCreateView(FormView):
model = GCMDevice
form_class = DeviceForm
template_name = "deviceobj_form.html"
def form_valid(self, form):
reg_id = self.request.POST.get('registration_id','')
dev_id = self.request.POST.get('device_id','')
if is_hex(dev_id):
GCMDevice.objects.create(registration_id=reg_id, device_id=dev_id)
else:
GCMDevice.objects.create(registration_id=reg_id, device_id='abcdef2222')
return render_to_response('success.html',RequestContext(self.request,{}))
def is_hex(s):
try:
int(s,16)
return True
except ValueError:
return False
What am I doing wrong in 3rd choice?

Was an error in the push-notifications library I was using, not in my code posted above. With either postgres or mysql as the database engine, attempting to create or update a gcm device using a form and http post was failing. The validators were being run against a string value, thus min and max validation were failing. Rewrote the validators to run against an integer value.

Related

Flask_WTF / Python: init value turns to None if HTML POST is performed

I am working on a quick project to train my Python/Flask/Flask_WTF skills.
Unfortunately, I have spent 2 days without understanding the behavior of my code and the different values.
Here is the simplified code (all modules loaded) for a better visibility for you.
Note: MovieDB is a class inherited from db.model (SQLAlchemy) with different keys (no issue here)
[MAIN.PY]
#app.route("/")
def home():
##### Test init of DB to check above is correct
if not Path("instance/movies.db").exists():
print("OK")
db_test_init()
##### End of test init
return render_template("index.html", DB=MovieDB.query.filter_by().all())
The issue comes in the route below.
#app.route("/edit", methods=["GET", "POST"])
def edit():
movie_id = request.args.get("id")
movie_edit = MovieDB.query.get(movie_id)
print(movie_id, movie_edit)
form_edit = EditForm() # creating a form object (here 2 fields and a submit button)
if form_edit.validate_on_submit(): # if button is pushed
print(movie_id, MovieDB.query.get(movie_id))
# movie_to_edit.rating = form_edit.rating.data
# movie_to_edit.review = form_edit.review.data
db.session.commit()
return redirect(url_for("home"))
return render_template("edit.html", movie=movie_edit, form=form_edit)
[EDIT.HTML]
{% block content %}
<div class="content">
<h1 class="heading">{{ movie.title }}</h1>
<p class="description">Edit Movie Rating</p>
<form action="{{ url_for('edit') }}" method="POST">
{{ form.csrf_token }}
{{ render_form(form, button_style='btn btn-outline-dark btn-lg') }}
<input type='hidden' name="id" value="{{ movie.id }}" />
</form>
</div>
{% endblock %}
Now what happens in the "edit" route:
if I use a GET
first print returns: 1 <MovieDB 1> ==> OK
if I use a POST (upon submit button in the form)
first print now returns: None None
second print returns: None None
How come the first print gets a different value even before getting into the statement
if form_edit.validate_on_submit():
request.args contains optional query parameters appended to the URL after a question mark. Typically used for GET requests, such as search queries.
http://localhost:5000/edit?arg0=0&arg1=1
If you enter a URL in the action attribute of the form using url_for without setting the id attribute, this will not be included in request.args. For this reason, None is returned for both values.
Pass the id to url_for and this will be appended to the url.
url_for('edit', id=movie.id)
With variable rules there is a possibility to assume the id within the URL.
#app.route('/edit/<int:id>', methods=['GET', 'POST'])
def edit(id):
# your code here
As described above, you can assign the movie.id to the attribute id within url_for and this will be integrated into the path of the URL.
In my opinion, the most sensible way to transmit the id to the server for this purpose.
request.form contains the form data and can be used in the same way as request.args. However, the hidden input field is ignored by you within the endpoint. But it is another possible option to transfer the data.
There are also other attributes of the request object that can be used for other purposes, such as transferring files or receiving data in JSON format.

Passing a list of strings (uuids) back and forth between django views and template

On one of the pages of my django project I have a check box to select uuids corresponding to a model. This is what I have on the views.py page and it works well to create a checkbox.
def template_page(request, uuid_selects=None, option_1=False):
...
class uuidCheckBox(forms.Form):
uuid_selects =
forms.ModelMultipleChoiceField(queryset=uuid_model,
widget=forms.CheckboxSelectMultiple)
uuid_select_field = uuidCheckBox()
args.update({'uuid_select_field': uuid_select_field})
...
render(request, landing.html, args)
On my template page I have this:
<form action='get_uuid_selects' method='GET'>
{{ uuid_select_field }}
<input type='submit' value='Submit' />
</form>
Which redirects back to this view:
def get_uuid_selects(request):
uuid_selects = request.GET.getlist('uuid_selects')
return landing_page(request, uuid_selects)
This all works fine however when I try to pass this list of uuids back to the template as a hidden value and return it when the user accesses another form the list doesn't work. For example I pass the list as an argument following the approach here This is the views.py for template:
def template_page(request, uuid_selects=None, option_1=False):
...
if uuid_selects:
args.update({'uuid_selects': json.dumps(uuid_selects)})
...
render(request, landing.html, args)
Then I pass this list of uuids back to the template page so that it is a hidden value in another form on the template.html page.
<form action='to_option_1' method='GET'>
<button type='submit'>Option 1</button>
{% if uuid_selects %}
<input type="hidden" value= {{ uuid_selects }} name="uuid_selects">
{% endif %}
</form>
Then this is where the error surfaces once I've passed the list of uuids back to the views.py
def to_option_1(request):
option_1 = True
try:
uuid_selects = request.GET.getlist('uuid_selects')
except KeyError:
uuid_selects = None
return team_landing_page(request,
uuid_selects = uuid_selects,
option_1 = True)
The GET request only returns the first uuid (the list I tried was longer than 1) and in the wrong form to read as a uuid (this causes an error later on in the views.py but that's not relevant it's clear from the GET response that this is where the error occurs.:
['“["4322ac69-4c96-4fc1-b901-d68b5nnb0329",”
Clearly it has something to do with string formatting but I can't figure out how to make this work especially since passing the list of uuids works the first time when it just has to go from the HTML template back to the views.py - it's only once I repeat the process that things stop working.
Sorry if this is too much code just wanted to be very clear what the issue is.
Maybe the error is using getlist instead of just get ?
uuid_selects = request.GET.get('uuid_selects')
You might need to change your function uuid_selects argument into:
def template_page(request, uuid_selects, option_1=False):
The solution (thank you to Benbb96 for providing a few key tips) is to store the list of UUIDs as a JSON string back and forth. The input needs single quotes around it in the template.html page so that it is read in as a string.
<input type="hidden" value= '{{ uuid_selects }}' name="uuid_selects">
Then in the views.py function go_to_option_1 it should be read back in as a string not using the getlist function.
uuid_selects = request.GET('uuid_selects')
Finally to read the string back as a python list you need to parse the JSON string.
uuid_selects = json.loads(uuid_selects)

Clean Django form fields with same name

I have a django template in which I'm dynamically rendering multiple fields (using ajax)
Below is a Django form (which has been rendered in a template) whose fields have same names. I want to use the cleaned_data method to clean form data in views.py before storing them in the database.
index.html
<div class="form-container">
<!-- ASSUMING I HAVE ALREADY ADDED FIELDS DYNAMICALLY -->
<form id = "orderForm" action="newPickupOrder/" method="post" name="processForm">
<input type='text' name='this_field'>
<input type='text' name='this_field'>
<button type="submit">Submit</button>
</form>
</div>
<form id="addItemForm">
{% csrf_token %}
<!-- BUTTON TO ADD MORE FIELDS DYNAMICALLY -->
<button id = "addItemButton">Add item</button>
</form>
<script>
var addItemButton = document.querySelector('#addItemButton');
addItemButton.onclick = function(){
$.ajax({
type: 'POST',
url: 'addItem/',
data: addItemForm.serialize(),
success: function (response) {
$("#orderForm").append(response);
console.log('Success');
},
error: function (response) {
console.log('Error = '+response);
}
});
};
</script>
forms.py
class ItemForm(forms.Form):
this_field = forms.CharField()
urls.py
urlpatterns = [
url(r'^newPickupOrder/$', views.pickup_order_view, name='new_pickup_order'),
]
views.py
def add_item(request):
if request.method == 'POST':
itemForm = ItemForm()
return HttpResponse(itemForm.as_p())
def pickup_order_view(request):
if request.method == 'POST':
form = ItemForm(request.POST)
same_name_fields = request.POST.getlist('this_field')
# WANT TO CLEAN DATA IN same_name_fields
if form.is_valid():
print(form.cleaned_data)
# ONLY PRINTS THE LAST FIELD's DATA
return HttpResponseRedirect('/viewPickupRequests')
The problem I'm facing is that if I use form.cleaned_data['this_field'], only the last field's data is fetched i.e. in this example, the field with value anotherTestValue is fetched and cleaned. If I fetch the data using request.POST.getlist('this_field'), all the fields' data is fetched and stored as a list, but, I don't know how to clean it using cleaned_data method. Is there a way to apply the cleaned_data method to the list of field data?
I'm sorry, I can't test if this works so this is not really an answer - but the comment system is not suitable for larger code chunks so I'm posting here.
Django forms lack a field type that renders to multiple text inputs with the same name. The proper thing to do would be to write a new form field class and a new widget. Since you are not rendering the form in the template (you are using it only for validation) I will omit the widget part.
class AcceptAnythingMultipleChoiceField(forms.MultipleChoiceField):
def validate(self, value):
if self.required and not value:
raise ValidationError(
self.error_messages['required'],
code='required'
)
Then use this field class instead of forms.CharField() (you may need to pass an empty choices parameter).
[update]
So essentially what you're saying is that I need to create new form field class and then render it to the template each time the user wants to add a new field? What if user has to add 15 fields, I'll need to create 15 classes then! I think this method won't be suitable in scenarios where number of fields required to be generated is large. I feel there should be some elegant way to do this which i'm not aware of – The OP
No, it is not what I'm saying. You probably want to subclass something like MultipleHiddenInput and set AcceptAnythingMultipleChoiceField.widget to it. You will have to create a new template based on the template for MultipleHiddenInput and replace input type="hidden" for type="text" (the original template is django/forms/widgets/multiple_hidden.html).
class AcceptAnythingWidget(MultipleHiddenInput):
template_name = 'django/forms/widgets/multiple_visible.html'
class AcceptAnythingMultipleChoiceField(forms.MultipleChoiceField):
widget = AcceptAnythingWidget
def validate(self, value):
if self.required and not value:
raise ValidationError(
self.error_messages['required'],
code='required'
)
This should render as many <input name='this_field'> as needed for instantiated forms at the frontend if you use:
{{ form.this_field }}
in the template, but will not add/remove them dynamically.
In order to do that you must plug in the JavaScript required to add/remove inputs dynamically in the widget but I will left this as an exercise for you. Look at Form Assets (the Media class) in the docs in order to figure out how to do that.
I think that what you are looking for is formsets. https://docs.djangoproject.com/en/2.0/topics/forms/formsets/
from django.forms import formset_factory
ItemFormSet = formset_factory(ItemForm, extra=2)
You can the essentialy use ItemFormSet in the way you would use a normal form except that this objects is iterable.
You will also have to change your jquery if you want to dynamically add items. There are many examples online on how to do this. In short what you do is
clone one of the forms in the formset
clear all the values from the copied form
update the input's (prefixes of) id's
Using Formsets doesn't solve the problem of fetching and validating
fields with same name. The issue still remains
It does however generate the end result you wanted (see below). My question would be why you need to have inputs with the same name? If there is some jquery stuff that uses these names I dont see any reason why you wouldn't be able to use name like... or assign a class to the inputs instead.
def pickup_order_view(request):
if request.method == 'GET':
ItemFormSet = formset_factory(ItemForm, extra=5)
item_formset = ItemFormSet()
template = "some_template.html"
template_context = {'item_formset': item_formset}
return render(request, template, template_context)
if request.method == 'POST':
ItemFormSet = formset_factory(ItemForm)
item_formset = ItemFormSet(request.POST)
same_name_fields=[]
if item_formset.is_valid():
for item_form in item_formset:
same_name_fields.append(item_form.cleaned_data['this_field'])
print(same_name_fields)
Template
<form id = "orderForm" action="newPickupOrder/" method="post" name="processForm">
{% csrf_token %}
{{ item_formset.management_form }}
{{ for item_form in item_formset }}
{{ item_form.as_p }}
{{ endfor }}
<input type='submit' value='submit'>
</form>
Go to newPickupOrder/ , fill in the 5 fields, hit submit, and watch it print your list.

How can I edit an object without saved it in Django

There is a problem with my Django project, when I add an object it saves immediatly after that I will be redirected by object id to server_edit where I can fill fields. If I fill no fields and push "back" (go to previous page) browser button object will be saved without any data even if Save button was not pushed on template.
Is there any way do not save object where no fields was filled?
How can I edit an object without saved it?
I have a model "Server" that contains a few CharField
class Server(models.Model):
name = models.CharField(max_length=256)
I add and save an object:
def server_add(request):
server = Server()
server.save()
return HttpResponseRedirect(reverse('server:server_edit', args=(server.id,)))
after this I redirect to edit page:
def server_edit(request, server_id):
server = get_object_or_404(Server, pk=server_id)
return render(request, 'server/server_edit.html'{'server': server})
Fields will be edited on html template:
<form action="{% url 'server:server_edit_post' server.id %}" method="post">
{% csrf_token %}
<tr>
<td>{% trans "Name:" %}</td>
<td><input type="text" name="name" maxlength="256" value="{{server.name}}" required></td>
</tr>
<button type="submit" class="btn btn-success">{% trans "Save" %}</button>
</form>
This view gets data from the template and allows to edit them:
def server_edit_post(request, server_id):
server= get_object_or_404(Server, pk=server_id)
name = request.POST['name']
server.name = name
server.save()
return HttpResponseRedirect(reverse('server:server_index', args=()))
You should avoid saving object and then filling it with data in different view.
Try using generic edit views such as CreateView/EditView or FormView with Django forms (https://docs.djangoproject.com/en/1.9/ref/class-based-views/generic-editing/).
Example:
class ServerCreateView(CreateView):
form_class = ServerCreateForm
template_name = 'servers/add.html'
With this, all validation is done automatically.
Http calls should be stateless. The connection can be dropped any time, which leaves the DB in the same inconsistent state which is what you try to avoid here. Instead of using the form which only contains name, you could just redirect the user to a new page with the form for the rest of the data with the previously entered name as a get parameter, and pre-fill that form with the name on that page

Django saving string without unicode characters

I want to save my string in CharField and then display it as a value of input.
But when I save it, I got value in unicode characters. ex.
When I type "a" and I save it I got "(u'a',)"
and I want to get only "a"
In my html code I have form:
<form action="{% url 'edit'%}" method="post">
{% csrf_token %}
<input type="text" name="name" value="{{data.name}}" placeholder="{% trans 'Name' %}" required><br>
<button type="submit">{% trans 'Submit'%}</button>
</form>
My views:
#login_required
def edit(request):
user = MyUser.objects.get(pk=request.user.pk)
if request.method == "POST":
post = request.POST
editmodel = user.editmodel_set.all()[0]
editmodel.name = post.get('name')
shop.save()
And models:
class EditModel(model.Model):
name = models.CharField(max_length=100)
user = models.ForeignKey(settings.AUTH_USER_MODEL)
That is not a good way of working with forms. Do not use data in request.POST directly to save in the database.
For more information refer working with forms in django documentation.
Either create a model form or simple form, check posted data is valid and then save it in DB with appropriate data object.
If you still want to do that update your code as
editmodel.name = post.getlist('name')[0]

Categories