Django Jquery Get URL Conf - ajax

Ok, so I'm trying to call the function
def user_timetable(request, userid):
user = get_object_or_404(TwobooksUser,id = userid)
timeSlots = TimeSlot.objects.filter(user = request.user)
rawtimeslots = []
for timeSlot in timeSlots:
newSlot = {
'userid': timeSlot.user.id,
'startTime': str(timeSlot.startTime),
'endTime': str(timeSlot.endTime),
}
rawtimeslots.append(newSlot)
return HttpResponse(simplejson.dumps(rawtimeslots))
through the javascript in
{% include 'elements/header.html' %}
<script type='text/javascript'>
$(document).ready(function() {
$.get('/books/personal{{ user.id }}/timetable/', {}, function(data) {
data = JSON.parse(data);
var events = new Array();
for (var i in data) {
events.push({
id: data[i].id,
title: '{{ request.user.name }}',
start: Date.parse(data[i].startTime, "yyyy-MM-dd HH:mm:ss"),
end: Date.parse(data[i].endTime, "yyyy-MM-dd HH:mm:ss"),
allDay: false
});
}
where the above exists in a template that's being rendered (I think correctly).
The url conf that calls the function user_timetable is
url(r'^books/personal/(?P<userid>\d+)/timetable/$',twobooks.ajax.views.user_timetable),
But, user_timetable isn't being called for some reason.
Can anyone help?
EDIT-
Ok the original problem was that the template was not being rendered correctly, as the url in firebug comes to '/books/personalNone/timetable/' , which is incorrect.
I'm rendering the template like this -
def renderTimetableTemplate(request):
#if request.POST['action'] == "personalTimetable":
user = request.user
return render_to_response(
'books/personal.html',
{
'user': user,
},
context_instance = RequestContext(request)
)
Is there a mistake with this?

There is a slash missing after "personal"
$.get('/books/personal{{ user.id }}/timetable/', {}, function(data) {
should be
$.get('/books/personal/{{ user.id }}/timetable/', {}, function(data) {
Btw. you should use the {% url %} template tag.

There is a mismatch between the data you're converting to JSON and passing to the script, and the data that the script is expecting. You are passing a userId element in each timeslot, whereas the script is expecting just id.
This error should have shown up in your browser's Javascript console, and would be even easier to see in Firebug (or Chrome's built-in Developer Tools).

Related

Next.js seems to cache files as in a route of _next/data/[path].json preventing getStaticProps from running in server side render

The issue appears to happen when I post the link on platforms like Discord and Slack, where then to produce a URL preview they send a request to the link. The link which in this case follows this structure (normal format) www.domain.com/ctg/[...ids].
Within [...ids] I either pass one of two ids for the same object, the object has the following structure:
type Catalogue {
id: ID!
edit_id: String!
user_id: String!
title: String
...
}
The first id I could pass into [...ids] would be Catalogue.id
The second id I could pass into [...ids] would be Catalogue.edit_id
Whenever either of those inputs for [...ids] is passed as part of a request the following getStaticProps is ran:
export const getStaticProps: GetStaticProps = async ({ params }) => {
const { ids } = params;
let catalogue: CatalogueType | null = await fetchFullCatalogue(ids[0]);
return {
props: {
catalogue_prop: catalogue,
params,
},
};
};
with fetchFullCatalogue being:
export const fetchFullCatalogue = async (
id: string
): Promise<CatalogueType | null> => {
let catalogue: CatalogueType;
const fetchToUrl =
process.env.NODE_ENV === "development"
? "http://localhost:4000/graphql"
: process.env.BACKEND_URL + "/graphql";
// create a axios fetch request to the http://localhost:4000/graphql
const query = `
<...SOME FRAGMENTS LATER...>
fragment AllCatalogueFields on Catalogue {
id
edit_id
user_id
status
title
description
views
header_image_url
header_color
author
profile_picture_url
event_date
location
created
updated
labels {
...AllLabelFields
}
listings {
...AllListingFields
}
}
query Catalogues($id: ID, $edit_id: String) {
catalogues(id: $id, edit_id: $edit_id) {
...AllCatalogueFields
}
}`;
const config: AxiosRequestConfig = {
method: "post",
url: fetchToUrl,
headers: {
"Content-Type": "application/json",
},
data: JSON.stringify({
query,
variables: { id: id, edit_id: id },
}),
};
let response = await axios(config);
if (response.data.errors) return null;
catalogue = response.data.data.catalogues[0];
console.log("catalogue", catalogue);
return catalogue;
};
The request it is making is to the following API endpoint
Query: {
catalogues: async (
_: null,
args: { id: string; edit_id: string }
): Promise<Catalogue[]> => {
let catalogues: Catalogue[];
// when both id and edit_are passed
if (args.id && args.edit_id) {
catalogues = await getFullCatalogues(args.id, "id", true);
// the following convoluted request is the result of
// me responding to the fact that only the edit_id was working
if (catalogues.length === 0) {
catalogues = await getFullCatalogues(args.edit_id, "edit_id", true);
if (catalogues.length === 0) {
throw new UserInputError("No catalogues found");
}
} else {
catalogues = await getFullCatalogues(
catalogues[0].edit_id,
"edit_id",
true
);
}
console.log("catalogues", catalogues);
} else if (args.id) {
catalogues = await getFullCatalogues(args.id);
} else if (args.edit_id) {
catalogues = await getFullCatalogues(args.edit_id, "edit_id");
} else {
const res = await db.query(fullCatalogueQuery());
catalogues = res.rows;
}
return catalogues;
},
...
},
This results in the following output within the deployed logs:
The logs show the data when the Catalogue is first created which simultaneously navigates me to the URL of "normal format" with Catalogue.id which is interpreted as /_next/data/qOrdpdpcJ0p6rEbV8eEfm/ctg/dab212a0-826f-42fb-ba21-6ebb3c1350de.json. This contains the default data when Catalogue is first generated with Catalogue.title being "Untitled List"
Before sending both requests I changed the Catalogue.title to "asd".
Notice how the request with the Catalogue.edit_id which was sent as the "normal format" was interpreted as /ctg/ee0dc1d7-5458-4232-b208-1cbf529cbf4f?edit=true. This resulted in the correct data being returned with Catalogue.title being "asd".
Yet the following request with the Catalogue.id although being of the same "normal format" never provoked any logs.
(I have tried sending the request without the params ?edit=true and the same happens)
Another important detail is that the (faulty) request with the Catalogue.id produces the (faulty) URL preview much faster than the request with Catalogue.edit_id.
My best theory as to why this is happening is that the data of the URL with Catalogue.id is somehow stored/cached. This would happen as the Catalogue is first created. In turn it would result in the old stored Catalogue.id being returned instead of making the fetch again. Whereas the Catalogue.edit_id makes the fetch again.
Refrences:
Live site: https://www.kuoly.com/
Client: https://github.com/CakeCrusher/kuoly-client
Backend: https://github.com/CakeCrusher/kuoly-backend
Anything helps, I felt like ive tried everything under the sun, thanks in advance!
I learned that For my purposes I had to use getServerSideProps instead of getStaticProps

How to render ajax response to view

Here is my predicament: I need to render json response received from controller method. I do this by calling clicking on navbar item "List Articles" which activate method ajaxIndex(). Then tat method makes request to route which in turn call controller method also called ajaxIndex(). That method then gater all articles and sends it as a response. After that, that response i can't control, it just renders raw json ...
Navbar item:
<a class="nav-link" href="/articles" onclick="ajaxIndex(this)"> List Articles </a>
Route:
Route::get('/articles', "ArticlesController#ajaxIndex");
Method in ArticlesController
public function ajaxIndex(Request $request)
{
$var1 = $request->var1;
$var2 = $request->var2;
$elem = $request->elem;
$currUser = auth()->user();
$currUri = Route::getFacadeRoot()->current()->uri();
$articles = Article::orderBy("created_at","desc")->paginate(5);
$html = view('articles.List Articles')->with(compact("articles", "var1", "var2", "elem", "currUser", "currUri"))->render();
//return $request;
return response()->json(["success"=> true, "html" => $html], 200);
//return response()->json(["success"=> $articles,"var1"=> $var1, "var2"=> $var2, "elem"=> $elem, "currUser" => $currUser, "currUri" => $currUri], 200);
}
and here my ajax method
function ajaxIndex(me,formId){
let var1 = "gg";
let var2 = "bruh";
let token = document.querySelector("meta[name='csrf-token']").getAttribute("content");
let url = "/articles";
if(formId){
let form = $("#"+formId).serialize();
console.log(form);
}
$.ajax({
type: "GET",
url: url,
headers:{
"X-CSRF-TOKEN": token
},
data: {/*
var1: var1,
var2: var2,
elem: {
id: me.id ? me.id : null,
class: me.className ? me.className : null,
value: me.value ? me.value : null,
innerHTML: me.innerHTML ? me.innerHTML : null,
}
*/},
success: (data) => {
console.log(data);
$('#maine').html(JSON.parse(data.html));
},
error: (data) => {
console.log(data);
}
});
}
How to render acquired data to particular view?
Now just renders json response alongside html.
My question is how to render response itself and where goes response from controller method. I tried console logging it when route is hit, but there is nothing in console. What is actual approach or what i need to change to achieve this?
Addendum: "For List Articles you will send ajax request to rest api where it returns array of objects(articles)". I assumed i needed to make ajax request, after being sent to appropriate blade, i should now display sent data? Am i getting wrong something? ...
Edit1:
Now when i go to any page in my app, for example:
http://articleapp.test/articles?page=2
it shows json response:
Edit2:
I also modified my ajax method to correctly display current page for article listing. Problem start when try to go to next page.
Here is the code:
function ajaxIndex(me,formId){
let token = document.querySelector("meta[name='csrf-token']").getAttribute("content");
let url = "/articles";
if(formId){
let form = $("#"+formId).serialize();
console.log(form);
}
$.ajax({
type: "GET",
url: url,
headers:{
"X-CSRF-TOKEN": token
},
data: {},
success: (data) => {
console.log(data);
let html = "<div class='container'>";
let articleBody = "";
let pagination = "<ul class='pagination'><li class='page-item'><a class='page-link' href='#'>Previous</a></li>";
if(data.articles.data.length > 0){
for(let i=0;i<data.articles.current_page;i++){
let created_at = data.articles.data[i].created_at.replace(/-/g,"/").split(" ")[0];
html += "<div class='row' style='background-color: whitesmoke;'><div class='col-md-4 col-sm-4'><a href='/articles/"+data.articles.data[i].id+"'><img class='postCover postCoverIndex' src='/storage/images/"+data.articles.data[i].image+"'></a></div><div class='col-md-8 col-sm-8'><br>";
if(data.articles.data[i].body.length > 400){
articleBody = data.articles.data[i].body.substring(0, 400);
html += "<p>"+articleBody+"<a href='/articles/"+data.articles.data[i].id+"'>...Read more</a></p>";
}
else{
html += "<p>"+data.articles.data[i].body+"</p>";
}
html += "<small class='timestamp'>Written on "+created_at+" by "+data.articles.data[i].user.name+"</small></div></div><hr class='hrStyle'></hr>";
history.pushState(null, null, "/articles?page="+(i+1));
}
for(let i=0;i<data.articles.total;i++){
//console.log(data.articles.data[i].id);
pagination += "<li class='page-item'><a class='page-link' href='/articles?page="+(i+1)+"'>"+(i+1)+"</a></li>";
}
pagination += "<li class='page-item'><a class='page-link' href='#'>Next</a></li></ul>";
}
html+="<div class='d-flex' style='margin: 10px 0px;padding-top: 20px;'><div class='mx-auto' style='line-height: 10px;'>"+pagination+"</div></div></div>";
$('#maine').html(html);
//?page=2
},
error: (data) => {
console.log(data);
}
});
}
When i go to next page, it shows json response as i previously stated. Look in the image above. It won't render ...
In this case ajax response should contain only the real content you want to get with the assynchronous request (html tags inside body). Your #maine element should be a div or another structure capable of having html child tags.
Ps.: If you want to render the ajax response like another page by changing header tags and maybe even the http content type then the response should be load inside an iframe tag.
**Edit: ** In pratice, delete the previous content before body tag in the view returned by ajax. And #maine must be a to contain the ajax response.

Create a django url with ajax results

I am using AJAX to refresh a table in my template by using dataTable.
When I get the results from the AJAX call (json) I'm using 'columns' to set every row. In one of the rows, I want to create a Django URL to another view.
What I have tried so far doesn't seem to work
function refreshData(event_ref) {
var table = $('#datatable').dataTable({
'processing': true,
'serverSide': false,
'responsive': true,
'destroy': true,
'ajax': {
'type': "GET",
'url': "{% url 'events:index' %}",
'dataSrc': ""
},
'columns': [
{ "data": "pk", render: function (pk) { return '<input type="checkbox" id="check_' + pk + '" name="check_all">'} },
{ "data": "fields.date_time" },
{ "data": "pk", render: function (pk) { return "<a href='{% url 'events:event' " + pk + "%}'>Click me</a>" }}
],
'order': [[ 2, "desc" ]],
'columnDefs': [ {
'targets': [0, 8], // column index (start from 0)
'orderable': false, // set orderable false for selected columns
}],
'scroller': {
loadingIndicator: true
}
});
}
The problem is in the line
{ "data": "pk", render: function (pk) { return "<a href='{% url 'events:event' " + pk + "%}'>Click me</a>" }}
and what I get is this,
Reverse for 'event' with arguments '(' + pk + ',)' not found. 1 pattern(s) tried: ['events/event/(?P[0-9]+)/']
My urls.py file is this
url('event/(?P<event_id>[0-9]+)/', views.event, name='event'),
and my view.py is this
def index(request):
latest_event_list = events.objects.order_by('-date_time')
if request.is_ajax():
json = serializers.serialize('json', latest_event_list)
return HttpResponse(json, content_type='application/json')
template = loader.get_template('events/index.html')
context = {
'app_name': "events",
'page_name': "real-time",
'latest_event_list': latest_event_list,
'total_events': len(latest_event_list)
}
return HttpResponse(template.render(context, request))
def event(request, event_id):
latest_event_list = events.objects.order_by('-date_time')[:5]
template = loader.get_template('events/event.html')
context = {
'app_name': "events",
'page_name': "archive",
'latest_event_list': latest_event_list,
}
return HttpResponse(template.render(context, request))
How can I create a Django URL with a value from AJAX call?
First of all, passing arguments to the {% url %} template tag isn't done by appending a string, to get the url you want in a template, you do:
{% url 'events:event' event_id=pk %}
Second, any template tag you use in your HTML is interpreted in the back-end once by Django when the HTML page is rendered. That is, before it gets to the browser and javascript starts running (the front-end). So what you're doing makes no sense, because you want javascript to dynamically change the url in the button.
If you look at the source of your HTML in your browser you'll see there's no template tag.
So you have to construct the url in javascript. What you could do is create a javascript variable in your template that is "{% url 'events:event' event_id=1 %}" (which in the HTML file parsed by the browser would be events/event/1 and then using string manipulation replace the "1" with the value of pk.
you catch the error because call tag when the pk is not defined, so you can fix it by using some base url and then replace pk by current value, for example:
{
var base_url = "{% url 'events:event' 0 %}";
var url = base_url.substring(0, base_url.lastIndexOf('/') +1 ) + pk;
return "<a href='" + url + "'>Click me</a>"
}

{{ obj.data }} not displaying in vue template after axios get

I am trying to build a carousel using Laravel 5.6 and Vue.js. I can call the data from db, and console.log response.data. It works like expect it to. Script below.
<script>
$(document).ready(function() {
$("#availability").owlCarousel();
});
export default {
props: {
areaId: null,
tutorId: null,
},
data () {
return {
availability: []
}
},
methods: {
getAvailability () {
var that = this;
axios.get( '/' + this.areaId + '/' + this.tutorId + '/availability').then((response) => {
console.log(response.data)
that.availability = response.data;
});
}
},
mounted () {
this.getAvailability();
}
}
</script>
Now I would expect to be able to display the data like
{{availability.monday_begin}}
but it displays nothing, empty tags.
When I add a second .data to response, so change
that.availability = response.data;
to
that.availability = response.data.data;
The data object shows up as undefined in the vue-dev tools, and {{ availability.monday_begin }} throws an error cannot read property of undefined.
I also tried adding a v-if to the template, based on a couple different articles I read, but nothing I tried worked.
Thanks for your help.

Vue.js Retrieving Remote Data for Options in Select2

I'm working on a project that is using Vue.js and Vue Router as the frontend javascript framework that will need to use a select box of users many places throughout the app. I would like to use select2 for the select box. To try to make my code the cleanest I can, I've implemented a custom filter to format the data the way select2 will accept it, and then I've implemented a custom directive similar to the one found on the Vue.js website.
When the app starts up, it queries the api for the list of users and then stores the list for later use. I can then reference the users list throughout the rest of the application and from any route without querying the backend again. I can successfully retrieve the list of users, pass it through the user list filter to format it the way that select2 wants, and then create a select2 with the list of users set as the options.
But this works only if the route that has the select2 is not the first page to load with the app. For example, if I got to the Home page (without any select2 list of users) and then go to the Users page (with a select2), it works great. But if I go directly to the Users page, the select2 will not have any options. I imagine this is because as Vue is loading up, it sends a GET request back to the server for the list of users and before it gets a response back, it will continues with its async execution and creates the select2 without any options, but then once the list of users comes back from the server, Vue doesn't know how to update the select2 with the list of options.
Here is my question: How can I retrieve the options from an AJAX call (which should be made only once for the entire app, no matter how many times a user select box is shown) and then load them into the select2 even if the one goes directly to the page with the select2 on it?
Thank you in advance! If you notice anything else I should be doing, please tell me as I would like this code to use best practices.
Here is what I have so far:
Simplified app.js
var App = Vue.extend({
ready: function() {
this.fetchUsers();
},
data: function() {
return {
globals: {
users: {
data: []
},
}
};
},
methods: {
fetchUsers: function() {
this.$http.get('./api/v1/users/list', function(data, status, response) {
this.globals.users = data;
});
},
}
});
Sample response from API
{
"data": [
{
"id": 1,
"first_name": "John",
"last_name": "Smith",
"active": 1
},
{
"id": 2,
"first_name": "Emily",
"last_name": "Johnson",
"active": 1
}
]
}
User List Filter
Vue.filter('userList', function (users) {
if (users.length == 0) {
return [];
}
var userList = [
{
text : "Active Users",
children : [
// { id : 0, text : "Item One" }, // example
]
},
{
text : "Inactive Users",
children : []
}
];
$.each( users, function( key, user ) {
var option = { id : user.id, text : user.first_name + ' ' + user.last_name };
if (user.active == 1) {
userList[0].children.push(option);
}
else {
userList[1].children.push(option);
}
});
return userList;
});
Custom Select2 Directive (Similar to this)
Vue.directive('select', {
twoWay: true,
bind: function () {
},
update: function (value) {
var optionsData
// retrive the value of the options attribute
var optionsExpression = this.el.getAttribute('options')
if (optionsExpression) {
// if the value is present, evaluate the dynamic data
// using vm.$eval here so that it supports filters too
optionsData = this.vm.$eval(optionsExpression)
}
var self = this
var select2 = $(this.el)
.select2({
data: optionsData
})
.on('change', function () {
// sync the data to the vm on change.
// `self` is the directive instance
// `this` points to the <select> element
self.set(select2.val());
console.log('emitting "select2-change"');
self.vm.$emit('select2-change');
})
// sync vm data change to select2
$(this.el).val(value).trigger('change')
},
unbind: function () {
// don't forget to teardown listeners and stuff.
$(this.el).off().select2('destroy')
}
})
Sample Implementation of Select2 From Template
<select
multiple="multiple"
style="width: 100%"
v-select="criteria.user_ids"
options="globals.users.data | userList"
>
</select>
I may have found something that works alright, although I'm not sure it's the best way to go about it. Here is my updated code:
Implementation of Select2 From Template
<select
multiple="multiple"
style="width: 100%"
v-select="criteria.reporting_type_ids"
options="globals.types.data | typeList 'reporttoauthorities'"
class="select2-users"
>
</select>
Excerpt from app.js
fetchUsers: function() {
this.$http.get('./api/v1/users/list', function(data, status, response) {
this.globals.users = data;
this.$nextTick(function () {
var optionsData = this.$eval('globals.users.data | userList');
console.log('optionsData', optionsData);
$('.select2-users').select2({
data: optionsData
});
});
});
},
This way works for me, but it still kinda feels hackish. If anybody has any other advice on how to do this, I would greatly appreciate it!
Thanks but I'm working on company legacy project, due to low version of select2, I encountered this issue. And I am not sure about the v-select syntax is from vue standard or not(maybe from the vue-select libaray?). So here's my implementation based on yours. Using input tag instead of select tag, and v-model for v-select. It works like a charm, thanks again #bakerstreetsystems
<input type="text"
multiple="multiple"
style="width: 300px"
v-model="supplier_id"
options="suppliers"
id="select2-suppliers"
>
</input>
<script>
$('#app').ready(function() {
var app = new Vue({
el: "#app",
data: {
supplier_id: '<%= #supplier_id %>', // We are using server rendering(ruby on rails)
suppliers: [],
},
ready: function() {
this.fetchSuppliers();
},
methods: {
fetchSuppliers: function() {
var self = this;
$.ajax({
url: '/admin_sales/suppliers',
method: 'GET',
success: function(res) {
self.suppliers = res.data;
self.$nextTick(function () {
var optionsData = self.suppliers;
$('#select2-suppliers').select2({
placeholder: "Select a supplier",
allowClear: true,
data: optionsData,
});
});
}
});
},
},
});
})
</script>

Resources