Django and VueJS: How to implement a stream of content

By Samuel Muiruri | Dec. 20, 2018 | Django


Started out by implementing the same project with React now doing it with Vue. You can view the source code here. It’s looks the same

Screenshot of the main page.

With loaded content of 10 jokes from the Joke section once the page loads, you can also select the category and click refresh and get another 10 jokes. It also will query for new jokes every 30 seconds.

Starting with the base template uses the development version of Vue, I get an error with a “Reference Error: unable to find Vue” if I use the production version. It still runs/loads fast, faster than React’s implementation of dev.

<!DOCTYPE html>
<html>
<head>
 <title>{% block title %}{{ title }}{% endblock %}</title>
<meta charset="utf-8"/>
<link rel="stylesheet" type="text/css" href="/static/css/bootstrap.min.css">
 <link rel="stylesheet" type="text/css" href="/static/css/style.css">
 <link rel="stylesheet" type="text/css" href="/static/css/font-awesome.css">
<script type="text/javascript" src="/static/js/dev-vue.js"></script>
{% block css %} {% endblock %}
{% block js %}{% endblock %}
</head>
<body>
 <!-- Image and text -->
 <nav class="navbar navbar-light">
   <a class="navbar-brand" href="#">
     <img src="/static/img/bootstrap-solid.svg" width="30" height="30" class="d-inline-block align-top" alt="">
     Bootstrap
   </a>
 </nav>
{% block content %}{% endblock %}
</body>
</html>

Then there’s the home template that extends the base template, with the script that has the VueJS code and html with some parts bound to VueJS.

For example this code

v-on:click="first_active = true, second_active = false, third_active = false" v-bind:class="{active: first_active}"

in the label on click changes the value of some variables and bind also will add a class status depending on whether the value of first_activeis true. That allows clicking the different buttons to toggle the classes between the different labels.

This also in the input field first binds a value to the input value. In actuality I really don’t have to do this since I only need the value when getting new jokes and it can be set and update without updating or linking it to a html element, but it really makes debugging go quicker.

It also using v-model on clicking any of the bootstrap radio buttons will update the value of checked in Vue to match the name of the radio button. By default it’s set to the default selected radio button Joke more on this later.

v-bind:ref_id="joke_id" checked v-model="checked"

Then on the refresh button

v-on:click="refresh"

on click will run the function refresh.

And finally

<div class="card col-md-7 margin-left" v-for="joke in jokes">
    <div class="card-body">
     [[ joke.text ]]
     <p><span class="badge badge-secondary">[[ joke.name ]]</span></p>
    </div>
   </div>

this is how the jokes are populated, with a for loop similar to django’s jinja template.

{% extends 'base.html' %}
{% block js %}
  <script type="text/javascript" src="/static/js/script.js"></script>
{% endblock %}
{% block content %}
  <div id="app" class="row top-padding">
   <div id="side-nav">
     <div class="btn-group btn-group-toggle" data-toggle="buttons">
       <label class="btn btn-secondary" v-on:click="first_active = true, second_active = false, third_active = false" v-bind:class="{active: first_active}">
         <input type="radio" name="options" id="option1" autoComplete="off" value="Joke" v-bind:ref_id="joke_id" checked v-model="checked" /> Joke
       </label>
       <label class="btn btn-secondary" v-on:click="first_active = false, second_active = true, third_active = false" v-bind:class="{active: second_active}">
         <input type="radio" name="options" id="option2" autoComplete="off" value="DadJokes" v-bind:ref_id="dadjokes_id" v-model="checked"/> DadJokes
       </label>
       <label class="btn btn-secondary" v-on:click="first_active = false, second_active = false, third_active = true" v-bind:class="{active: third_active}">
         <input type="radio" name="options" id="option3" autoComplete="off" value="Tweet" v-bind:ref_id="tweet_id" v-model="checked"/> Tweet
       </label>
     </div>
     <div class="col-md-12 top-padding" align="center">
       <button class="btn btn-primary" v-on:click="refresh">Refresh</button>
     </div>
 </div>
<div class="card col-md-7 margin-left" v-for="joke in jokes">
    <div class="card-body">
     [[ joke.text ]]
     <p><span class="badge badge-secondary">[[ joke.name ]]</span></p>
    </div>
   </div>
  </div>
{% endblock %}

Vue the way I’ve initialized, I’ve changed the delimiter to [[ not to interfere with django’s way of doing things. The initial data in the data dict is similar to React with the jokes as the list of jokes, joke_id , dadjokes_id and tweet_id as the id of where to start looking for the joke. first_activesecond_active and third_active as shown before deal with assigning the class active to the right radio button. Finally checked is the name of the checked radio button with an initial value in case none has been clicked yet.

window.onload = function () {
  var app = new Vue({
    delimiters: ['[[', ']]'],
    el: '#app',
    data: {
      jokes: [],
joke_id: 1,
      dadjokes_id: 1,
      tweet_id: 1,
first_active: true,
      second_active: false,
      third_active: false,
checked: 'Joke',
    },
    created: function() {
     this.postdata(initial_data = {'id': 1, 'model-name': 'Joke'})
var self = this;
      
      setInterval(function(){ //get new content after 30 seconds 
          self.refresh() 
      }, 30000);
    },
    methods: {
   postdata: function (retrieve_data = []){
    initial_data = {'id': 1, 'model-name': 'Joke'}
if (typeof retrieve_data == "object") {
     initial_data = retrieve_data
    }
fetch("\start-jokes\/", {
           body: JSON.stringify(initial_data),
           cache: 'no-cache', 
           credentials: 'same-origin', 
           headers: {
            'user-agent': 'Mozilla/4.0 MDN Example',
            'content-type': 'application/json'
           },
           method: 'POST',
           mode: 'cors', 
           redirect: 'follow',
           referrer: 'no-referrer',
           })
           .then(response => response.json()).then((json) => {
              this.jokes.unshift(...json['jokes'])
if (initial_data['model-name'] == 'Joke') {
               this.joke_id = json['ref-id']
              } else if (initial_data['model-name'] == 'DadJokes') {
               this.dadjokes_id = json['ref-id']
              } else if(initial_data['model-name'] == 'Tweet') {
               this.tweet_id = json['ref-id']
              }
        })
   },
   refresh: function() {
    var id;
    
    if (this.checked == 'Joke') {
     id = this.joke_id
    } else if (this.checked == 'DadJokes') {
     id = this.dadjokes_id
    } else if (this.checked == 'Tweet') {
     id = this.tweet_id
    }
var initial_data = {'model-name': this.checked, 'id': id};
    this.postdata(initial_data)
   }
  }
  });
 };

created is where you put what you want to run once Vue has finished loading. It runs postdata which queries the db for new jokes and using setInterval runs refresh same as clicking the refresh button and returns new jokes every 30 seconds.

postdata gets the new jokes, inserts them at the start of the existing list and updates one of the three id’s depending on which was selected and it’s name sent with the ajax post to the new id of where the query stopped.

refresh gets the current selected radio button’s name, then get’s it’s id and sends this info to postdata which gets back content and updates the old id with the new one once it’s done.

For the views nothing has changed so I’m just going to copy > paste the text from the react post below.

I found a dataset of jokes saved into a csv that gave me good content to populate the site with. Live demo of the site here and thesource codeavailable.

I separated them into three classes: Joke which includes R rated jokes,DadJokes PG rated jokes “mainly” and Tweet short and to the point jokes.All the models have one TextField for the joke with a unique rule to avoid duplicates.

from django.db import models
class DadJokes(models.Model):
 text = models.TextField(unique=True)
def __str__(self):
  return self.text
class Tweet(models.Model):
 text = models.TextField(unique=True)
def __str__(self):
  return self.text
class Joke(models.Model):
 text = models.TextField(unique=True)
def __str__(self):
  return self.text

The page that serves the html only gets the title from django dynamically, the rest of the content I’ll load with an ajax request from the server.

def home(request, template_name="home.html"):
 context = {'title': 'Jokes Central'}
 return render_to_response(template_name, context)

The url postdata connects to the view joke_list , with ajax and for now since I’ve not worked on sending the csrf_token through the fetch request I’ve used csrf_exempt also on the view.

It get’s the model_name and ref_id, get’s back the actual model by using theload_model which returns back the model. The model and ref_id are passed to get_jokes that queries the model and returns 10 new jokes (or less if you’ve exhausted all the jokes, but they’re quite a lot).

The response from get_jokes , a new ref_id and jokes is added to the response dict and sent back.

@csrf_exempt
def joke_list(request):
    response = {'status': None}
if request.method == 'POST':
        data = json.loads(request.body)
        ref_id, model = data['id'], load_model(data['model-name'])
        response['jokes'], response['ref-id'] = get_jokes(model, ref_id, 10, data['model-name'])
        response['status'] = 'ok'
else:
        response['error'] = 'no post data found'
return HttpResponse(
            json.dumps(response),
            content_type="application/json"
        )

Load model has a dict with a key as the model_name and the model itself. First it checks to see if the provided name is in the model keys, if not returns the joke model else it uses the dict to query the model and return it.

get_jokes with the model, ref_id, maximum number of jokes to return and the model_name. It gets from the model the last_id of the model so the query doesn’t look beyond that.

inside a while loop it starting with the ref_id increments by 1 and checks to see if there’s an instance of this in the model. For every instance found thecount is incremented by 1 so it will keep running until it reaches the maximum needed or ref_id is > last_id of the last added joke.

def load_model(model_name):
    models = {
        'Joke': Joke,
        'DadJokes': DadJokes,
        'Tweet': Tweet
    }
if model_name not in models.keys():
        return Joke
    else:
        return models[model_name]
def get_jokes(model, ref_id, maximum, model_name):
    count = 0
    list_jokes = []
    last_id = model.objects.latest('id').id
    while count < maximum and ref_id <= last_id:
        try:
            joke = model.objects.get(id=ref_id)
            joke_dict = {'key': '{0}-{1}'.format(model_name, joke.id), 'text': joke.text, 'name': model_name}
            list_jokes.append(joke_dict)
            count += 1
        except ObjectDoesNotExist:
            pass
ref_id += 1
return list_jokes, ref_id