How to set vue data to the value of a form? - flask

I have a flask page that is for editing blog posts. It has the following vue:
<form method="POST" action="{{ url_for('edit',itemid=item.id) }}" id="text-input">
{{ form.csrf_token }}
<div style="margin-left:30px;margin-top:20px;">
Title: {{ form.title }}
</div>
<br/>
<div id="editor">
Content: {{ form.content( **{':value':'input','#input': 'update'}) }}
<div v-html="compiledMarkdown"></div>
</div>
<br/>
Category: {{ form.category|safe }}
<br/>
<input type="submit" value="Save">
</form>
<script>
new Vue({
el: '#editor',
data: {
input: "starting data"
},
computed: {
compiledMarkdown: function () {
return marked(this.input, { sanitize: true })
}
},
methods: {
update: _.debounce(function (e) {
this.input = e.target.value
}, 300)
}
});
</script>
What I would like to do is have a starting value for input based on something sent in by flask. Basically I would change input: "starting data" to input: {{ form.content.data }}. However, when I do this, it stops updating the input when I change the text in the box. I think I am kind of hardcoding the data to be whatever what in form.content.data as opposed to a string.
How can I pass this in so that it starts with the form.content.data value yet is still changeable?

The reason it didn't work was because {{ form.content.data }} appears in the template as raw text.
Thus it was trying to use something like: the brown fox jumped over the lazy dog
and this doesn't compile to a javascript object. Adding quotes around the {{ form.content.data }} like '{{ form.content.data }}' fixed it.

Related

How to get value from input and send it as json object to backend in vue 3

I have a questionnaire which shows questions and their answers with vuejs.
django will handle the backend.
I want to get the question id and answer from the questionnaire and send it to backend as json object.
here is my question component:
<template>
<div>
<div v-for="section in sections.slice(sectionStart, sectionEnd)" :key="section.id">
<div v-for="question in section.questions" :key="question.id">
<!-- Single Choice -->
<div v-show="question.type === 'Single Choice'" :id="question.id">
<p class="py-4 font-medium leading-8">{{ question.question_text }}</p>
<input type="hidden" :value="question.question_text" v-model="question.id">
<div class="flex py-1" v-for="choice in question.choices" :key="choice.id">
<input class="h-5 w-5" type="radio" :name="question.id" v-model="choice.id" :id="choice.id"/>
<label class="ml-3" :for="choice.id">{{ choice.text }}</label>
</div>
</div>
<!-- Multiple choice -->
<div v-show="question.type === 'Multiple Choice'" :id="question.id">
<p class="py-4 font-medium leading-8">{{ question.question_text }}</p>
<div class="flex py-1" v-for="choice in question.choices" :key="choice.id">
<input type="checkbox" class="h-6 w-6" :name="choice.id" :id="choice.id">
<label :for="choice.id" class="ml-3">{{ choice.text }}</label>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
sections: data.sections,
}
},
props:[
'sectionStart',
'sectionEnd'
]
};
</script>
Update 1
data.sections comes from django:
{% block extrahead %}
{% render_bundle 'index' 'css' %}
<script> let data = {{ data|safe }};</script>
{% endblock %}
and the structure of data:
{
section: [
0: {
id: 0,
desc: "...",
questions: [
0: {
id: 0,
text: "question text....?",
choices: [
0: {id: 0, text: "choice 1"},
1: {id: 1, text: "choice 2"},
2: {...}
]
},
1: {...},
]
},
1: {...},
2: {...}
]
}
Update 2
the json format I want to have:
responses: {
question_id: response_id,
}
How can I get question id and it's answer and save these value to json object and then send it to backend?
First you need to set up your v-model. Currently you refer to things like v-model="choice.id" which aren't existing in your data properties. How the structure of your v-model needs to be depends on how you want to pass the data.
As an example you could pass it like you already tried, with v-model. But instead of v-model="choice.id" we use v-model="userAnswers[section.id][question.id][choice.id]" after creating it in our data properties:
data() {
return {
sections: data.sections,
userAnswers: {}
}
}
So using userAnswers[section.id][question.id][choice.id] will generate something like this:
{
0: { // First section
0: { // First question
0: false, // Choices with true or false
1: true,
2: false,
3: false
}
},
1: {
...
}
}
EDIT: Usage with the provided structure
So you provided the following structure:
responses: {
question_id: response_id,
}
This is only possible for questions with only one answer and only if every answer has an unique id outside of their section. If the first question of a section always has 0 as their id, that won't work. But if they have, thatn it is simply this: userAnswers[question.id] as v-model of the radio buttons while they to provide the response_id as their values.

Jinja templating not working in script.js

I need to include jinja templating in element.innerHTML but jinja is not working.
Code script.js:
contentDiv.innerHTML = getContent(fragmentId);
function getContent(fragmentId) {
var pages = {
quad1: `
<form class="form-inline" method="POST" id="form1">
<h3>
<input type="number" id="quad_a1" name="input_a" class="form-control mx-2 col-1" placeholder="a">
<b>x² +</b>
<input type="number" id="quad_b1" name="input_b" class="form-control mx-2 col-1" placeholder="b">
<b>x +</b>
<input type="number" name="input_c" class="form-control mx-2 col-1" placeholder="c">
<b>=</b>
<input type="number" name="input_d" class="form-control mx-2 col-2" placeholder="Default(0)">
<button type="submit" class="btn btn-primary float-right mr-5" onclick="return empty_quad()">Solve</button>
</h3>
</form>
{{ sol }}
`,
.
.
.
};
return pages[fragmentId];
}
But the output is literally {{ sol }}, not the value of sol:
So how to access the variable sol passed through flask's render_template() in script.js?
You can't use jinja2 template in your js file.
First method: You have to use inline javascript in html file using <script></script> tag, and then you can access the sol variable by assigning it to javascript variable
<script> sol = "{{sol}}" </script> // {{sol}} should be between quotation marks
Second method: If you have seperate js file. you can make a div tag, define it's class and set it's id to {{sol}}. get the element by class name and then get it's id.
Html
<div class="myclass" id="{{sol}}" style="display:none"></div>
javascript
elem= document.getElementsByClassName("myclass") ;
console.log(elem.id) // this is the sol value.
json_script
Safely outputs a Python object as JSON, wrapped in a <script> tag, ready for use with JavaScript.
Argument: HTML “id” of the <script> tag.
For example:
{{ value|json_script:"hello-data" }}
If value is the dictionary {'hello': 'world'}, the output will be:
<script id="hello-data" type="application/json">{"hello": "world"}</script>
The resulting data can be accessed in JavaScript like this:
const value = JSON.parse(document.getElementById('hello-data').textContent);
XSS attacks are mitigated by escaping the characters “<”, “>” and “&”. For example if value is {'hello': 'world</script>&'}, the output is:
<script id="hello-data" type="application/json">{"hello": "world\\u003C/script\\u003E\\u0026amp;"}</script>
This is compatible with a strict Content Security Policy that prohibits in-page script execution. It also maintains a clean separation between passive data and executable code.
django doc

Displaying(adding, editing) and Deleting list items in multiple columns in Vue.js without v-if and v-for

I am beginner in Vue.js. I want to add new items in list, but these items need to be displayed in separate divs according to their "category" property. Also, every item has option to be edited (haven't made that yet) or deleted. I have read that it is not recommended to use v-if inside v-for, so inspired by second answer here I used Computed Properties to do that. I needed to add index for every list item, because I haven't found any way to delete list item in Vue.js without index. The problem is that we iterate over two lists from computed properties and basically we have repeating indexes (check out print of list items in my code and you will understand) so it deletes items from wrong category. This problem would make editing item names harder, too. I was thinking of way to solve this, but I would have to use v-for and v-if together which is not recommended.
Also, this is not very good solution for me, because I would probably need to generate these divs dynamically from given list of categories (there could be a lot of them), and I don't know how would I be able to generate computed properties dynamically for each category. For this I would need to use v-if inside v-for, which is not recommended.
So basically I have two problems:
1. Deleting items from wrong category
2. Dynamically generating divs for each category if I keep using this method with computed properties.
Here is my code: fiddle
Do you have any advice or solutions? Thanks in advance!
The index in the v-for has nothing to do with that of the object in the amenities array.
Since you don't want to make one loop for both categories, I can this solution:
A better approach would be to autogenerate a unique id for each object on addition, and then deleting according to it:
new Vue({
el: '#app',
data: function() {
return {
Amenity: {
name: '',
category: ''
},
amenities: [],
nextId:0
}
},
computed: {
familyCategory: function () {
return this.amenities.filter(i => i.category === 'family')
},
facilitiesCategory: function () {
return this.amenities.filter(i => i.category === 'facilities')
}
},
methods: {
addAmenity: function(e) {
this.amenities.push({
id: this.nextId,
name: this.Amenity.name,
category: this.Amenity.category
});
this.Amenity = {
name: '',
category: 'family'
};
this.nextId = this.nextId+1;
},
removeElement : function (id) {
console.log(id);
this.amenities=this.amenities.filter(e => e.id!=id);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<div id="app">
<br /> Amenity name:
<input type="text" v-model="Amenity.name" placeholder="Amenity name">
<label for="category">Category:</label>
<select id="cetegory" v-model="Amenity.category">
<option value="family">Family</option>
<option value="facilities" >Facilities</option>
</select>
<input type="button" #click="addAmenity" value="Submit" class="btn btn-info">
<div>
<h3>Family</h3>
<ol>
<li v-for="(item, index) in familyCategory">
{{ index }} - {{ item.name }}
<button>
Edit
</button>
<button v-on:click="removeElement(item.id)">
Delete
</button>
</li>
</ol>
</div>
<div>
<h3>Facilities</h3>
<ol>
<li v-for="(item, index) in facilitiesCategory">
{{ index }} - {{ item.name }}
<button>
Edit
</button>
<button v-on:click="removeElement(item.id)">
Delete
</button>
</li>
</ol>
</div>
</div>

Django - POST method not working for those forms created within a FOR loop in template

I'm using a for loop in a template to create multiple forms with method="post" that work with Ajax. But only the form for the first element of items_list works fine, the rest do not work at all showing error 405 0 Method Not Allowed. I think they all should work the same way. And just wondering if this issue was caused by a for loop or something else.
cart_items.html:
<script>
$(document).ready(function () {
$("#remove").click(function (event) {
event.preventDefault();
$.ajax({
url: '{% url "cart:remove_from_cart" %}',
type: "POST",
dataType: 'json',
data: {bookID: $('#idInput').val()},
success: function (response_data) {
alert('works fine')
},
error: function (response_data) {
console.log('error occurred');
}
});
});
});
</script>
{% for book in items_list %}
<div class="items">
<p id="title"> {{ book.book.title }}, quantity: {{ book.quantity }} </p>
<form method="post">
{% csrf_token %}
<input id="idInput" value="{{ book.book.id }}" >
<button id="remove" type="submit"> Remove</button>
</form>
</div>
{% endfor %}
The code in the function body below is just for testing. Once the first form works, I guess the problem was not caused by the function view.
cart/views.py:
#csrf_exempt
def remove_books(request):
cart = Cart.objects.get(user=request.user)
if request.method == 'POST':
passed_id = request.POST['bookID']
secured_id = int(passed_id)
response_data = {
'quantity': secured_id
}
return JsonResponse(response_data)
<script>
$(document).ready(function () {
$(".remove").click(function (event) {
// event.preventDefault(); // don't think it should be required with button type=button
var book_id = $(this).parent().find('.idInput').val(); // find correct input box.
var csrf = $('input[name="csrfmiddlewaretoken"]').val(); // get csrf token in variable.
// there are multiple ways to get csrf token, I personally like this ^^, see https://docs.djangoproject.com/en/2.1/ref/csrf/#ajax for more
$.ajax({
url: '{% url "cart:remove_from_cart" %}',
type: "POST",
dataType: 'json',
data: {
bookID: book_id,
csrfmiddlewaretoken: csrf // add csrf token to post data
},
success: function (response_data) {
alert('works fine')
},
error: function (response_data) {
console.log('error occurred');
}
});
});
});
</script>
{% csrf_token %} <!-- It will render a hidden input field with csrf token in it. Keep it outside for loop but in html. No need to render exactly same element multiple times. -->
{% for book in items_list %}
<div class="items">
<p class="title"> {{ book.book.title }}, quantity: {{ book.quantity }} </p>
<form method="post">
<input class="idInput" value="{{ book.book.id }}" > <!-- use class not id -->
<button class="remove" type="button"> Remove</button> <!-- you can use button type=button to avoid form submit and hence avoid event.preventDefault(); in js -->
</form>
</div>
{% endfor %}

Translate Django template to ReactJS

I've created some templates in Django, and I want to translate them to work in ReactJS. The thing that I'm struggling with the most is the regroup function. There's a number of ways I've thought of approaching it, but I think the easiest is probably to do it within the component. All I've managed to do is map the items which generates the entire list, but I need to have the items dynamically grouped before iterating each group.
In React I'd like to be able to apply a command like items.groupBy('start_time').groupBy('event_name').map(item =>
The output should be 'start_time', 'event_name', and then the rest of the data within each event group. Each 'start_time' will contain multiple events. I'd like to keep the code as concise as possible.
This is the Django template:
{% if event_list %}
<div id="accordian" class="panel-group" role="tablist" aria-multiselectable="true">
{% regroup event_list by start_time as start_time_list %}
{% for start_time in start_time_list %}
<div class="row start-time">
<div class="col">
<h6 class="text-muted">{{ start_time.grouper|date:' d-m-Y H:i' }}</h6>
</div>
</div>
{% regroup start_time.list by event as events_list_by_start_time %}
{% for event_name in events_list_by_start_time %}
<div class="panel panel-default">
<div class="card-header" id="{{ event_name.grouper|slugify }}">
<div class="panel-heading">
<h5 class="panel-title">
<a data-toggle="collapse" data-parent="#accordian" href="#collapse-{{ event_name.grouper|slugify }}">
{{ event_name.grouper|title }}
</a>
</h5>
</div>
</div>
<div id="collapse-{{ event_name.grouper|slugify }}" class="panel-collapse collapse in">
<div class="panel-body">
{% for item in event_name.list %}
# continue iterating the items in the list
This is the render method from the React component:
render() {
const { error, isLoaded, items, groups } = this.state;
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<div>
{items.map(item => (
<h4 key={item.id}>{item.event}</h4>
)
)}
</div>
);
}
}
}
If you are setting up a React front end, it would be more sensible not to mix django templates with the react front end components. Instead set up a Django/DRF backend api that will feed your react components with JSON data.
To translate this template from django to react, you just have to reimplement the regroup template tag as a javascript function. For pretty much any django template tag, you can easily find some javascript library that does the same. This is not included in React.js, but instead you can import utilities from libraries such as underscore or moment.js etc.
This is the sample django template code from the example in the documentation for the {% regroup %} template tag.
{% regroup cities by country as country_list %}
<ul>
{% for country in country_list %}
<li>{{ country.grouper }}
<ul>
{% for city in country.list %}
<li>{{ city.name }}: {{ city.population }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
Here's how you could do it with react.js
// React ( or javascript ) doesn't come with a groupby function built in.
// But we can write our own. You'll also find this kind of stuff in lots
// of javascript toolsets, such as lowdash, Ramda.js etc.
const groupBy = (key, data) => {
const groups = {}
data.forEach(entry => {
const {[key]: groupkey, ...props} = entry
const group = groups[groupkey] = groups[groupkey] || []
group.push(props)
})
return groups
}
// I'll define a dumb component function for each nested level of the list.
// You can also write a big render function with everything included,
// but I find this much more readable – and reusable.
const City = ({ name, population }) => (
<li> {name}: {population} </li>
)
const Country = ({ country, cities }) => (
<li>
{country}
<ul>{cities.map(props => <City key={props.name} {...props} />)}</ul>
</li>
)
const CityList = ({ cities }) => {
const groups = Object.entries(groupBy("country", cities))
return (
<ul>
{groups.map(([country, cities]) => (
<Country key={country} country={country} cities={cities} />
))}
</ul>
)
}
// We'll use the exact same data from the django docs example.
const data = [
{ name: "Mumbai", population: "19,000,000", country: "India" },
{ name: "Calcutta", population: "15,000,000", country: "India" },
{ name: "New York", population: "20,000,000", country: "USA" },
{ name: "Chicago", population: "7,000,000", country: "USA" },
{ name: "Tokyo", population: "33,000,000", country: "Japan" }
]
ReactDOM.render(<CityList cities={data} />, document.getElementById("app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<main id=app></main>
If you run the snippet above, you should get the exact same output as what you find in the original django example.
<ul>
<li>India
<ul>
<li>Mumbai: 19,000,000</li>
<li>Calcutta: 15,000,000</li>
</ul>
</li>
<li>USA
<ul>
<li>New York: 20,000,000</li>
<li>Chicago: 7,000,000</li>
</ul>
</li>
<li>Japan
<ul>
<li>Tokyo: 33,000,000</li>
</ul>
</li>
</ul>