My Worst CRUD Todo App : Django + Vue
Tags: django, vue
Published: 2021-09-14 18:07:17

# Introduction

I'm not doing Django, Vue.js recently. So, need to write a simple blog post to refresh things. Will use Vue.js in script mode, like jQuery or other JS libs, without any build tools. And I'm not going to use any auth for keeping it simple

This blog post teaches neither Django nor Vue. Feel free to visit some resources below
https://docs.djangoproject.com/en/3.2/intro/tutorial01/
https://tutorial.djangogirls.org/en/
https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Tutorial_local_library_website

https://vuejs.org/v2/guide/
https://www.vuemastery.com/courses/intro-to-vue-js/vue-instance/

## Disclaimer
This article **does NOT follow** any best practices. It teaches **neither Django nor Vue**. I'm just touching some basics to refresh my knowledge. That's the purpose of the article.

# Django side

## Install and start project
`pip install django`

`mkdir django-vue-project && cd django-vue-project/ && django-admin startproject config .`

`python manage.py startapp app1`

Add `app1` to `INSTALLED_APPS` in `settings.py`

After your edits, it would be something like below
``` python
# config/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    
    'app1',
]
```
Follow https://github.com/adamchainz/django-cors-headers and configure cors.

## Create Models

``` python
# app1/models.py
# Create your models here.
class Todo(models.Model):
    todo = models.TextField()
    is_done = models.BooleanField()
```

``` bash
python manage.py makemigrations
python manage.py migrate
```

# Enabling Django Admin, for Todo model

Create a superuser, so you can access admin pages. Note  both `username` and `password` will be `admin`
``` bash
python manage.py shell -c "from django.contrib.auth import get_user_model; User=get_user_model(); a=User.objects.create(username='admin'); a.set_password('admin'); a.is_superuser=True; a.is_staff=True; a.save()"
```

Edit admin.py to enable Todo manipulation in django admin pages
``` python
# app1/admin.py
# Register your models here.
admin.site.register(Todo)
```

# Run django dev server
``` bash
python manage.py runserver
```
Visit following URL to create few Todo items
http://localhost:8000/admin/app1/todo/

# Create List Todos End point

## Create a ListView

``` python
# app1/views.py
from django.views import View
from django.http import JsonResponse
from app1.models import Todo

# Create your views here.
class TodoListView(View):
    
    def get(self, request):
        todos = list(Todo.objects.values_list('id', 'todo','is_done',))
        return JsonResponse(todos,safe=False)
```

# CreateView

Change app1/views.py as follows.
``` python
# app1/views.py
# ...
from app1.models import Todo
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
# ...
@method_decorator(csrf_exempt, name='dispatch')
class TodoCreateView(View):

    def post(self, request):
        data = {
            'todo' : request.POST.get('todo'),
            'is_done': False
        }
        todo = Todo(**data)
        todo.save()
        return JsonResponse({'status':'saved','id':todo.id})
```

# UpdateView

Again change app1/views.py

```python
@method_decorator(csrf_exempt, name='dispatch')
class TodoUpdateView(View):

    def put(self, request, pk):
        todo = Todo.objects.get(pk=pk)
        
        # Workaround
        # https://dzone.com/articles/parsing-unsupported-requests-put-delete-etc-in-dja
        request.method = "POST"
        request._load_post_and_files()
        request.PUT = request.POST
        # End Workaround
        
        todo.todo= request.PUT.get('todo')
        todo.save()
        return JsonResponse({'status':'saved','id':todo.id})
```

#DeleteView

Change app1/views.py

```python
@method_decorator(csrf_exempt, name="dispatch")
class TodoDeleteView(View):
    def dispatch(self, request, *args, **kwargs):
        # https://micropyramid.com/blog/introduction-to-django-class-based-views/
        pk = kwargs.get("pk")
        return self.delete(request, pk)

    def delete(self, request, pk):

        # Workaround
        # https://dzone.com/articles/parsing-unsupported-requests-put-delete-etc-in-dja
        request.method = "POST"
        request._load_post_and_files()
        request.DELETE = request.POST
        # End Workaround

        todo_qs = Todo.objects.filter(pk=pk)
        is_todo_exists = todo_qs.exists()
        if is_todo_exists:
            todo = todo_qs.delete()
            return JsonResponse(
                {"status": "deleted"}
            )
        else:
            return JsonResponse({"status": "failed", "message": "deletion failed"})

```
# Vue Js Code

Just copy pasted the code here

```javascript
	var headers = { 'Content-Type': 'multipart/form-data' }
	var app = new Vue({
		el: '#app',
		data: {
			message: 'hello world',
			todos: [
				{
					pk: 1,
					todo: 'todo1',
					is_done: false,
				},
				{
					pk: 2,
					todo: 'todo2',
					is_done: false,
				},
				{
					pk: 1,
					todo: 'todo3',
					is_done: false,
				},
			],
			todo: null,
			pk: null,
		},
		methods: {
			reverseMessage: function () {
				this.message = this.message.split('').reverse().join('')
			},
			loadData: function () {
				var that = this
				axios.get('http://localhost:8000/').then(function (response) {
					console.log(response)
					that.todos = response.data
				})
			},
			saveThisTodo: function () {
				var that = this
				var todoData = new FormData();
				todoData.append('todo', this.todo)
				axios.post('http://localhost:8000/api/todo/create/', todoData, headers).then(function (response) {
					console.log(response)
					console.log('Saved')
					that.loadData()
				})
			},
			updateTodo: function () {
				var todoData = new FormData();
				todoData.append('todo', this.todo)
				var that = this
				axios.put('http://localhost:8000/api/todo/update/' + this.pk, todoData, headers).then(function (response) {
					console.log(response)
					console.log('Updated')
					that.loadData()
				})
			},
			editTodo: function (item) {
				console.log('Oh yeah: its time for edit')
				this.todo = item.todo
				this.pk = item.pk
			},
			deleteTodo: function (item) {
				var that = this
				axios.delete('http://localhost:8000/api/todo/delete/' + item.pk, headers).then(function (response) {
					console.log(response)
					console.log('Saved')
					that.loadData()
				})
			}
		},
		mounted: function () {
			this.loadData()
		}
	})
```

# Conclusion

This blog post only shares the distant glimpses of what I have done. To get full idea, visit github repo here https://github.com/ajeebkp23/worst-crud-todo-app-vue-django