วันเสาร์ที่ 14 เมษายน พ.ศ. 2561

nodejs Wordbucket

ทำตาม concept web เดิมของ django โดยใช้ nodejs express+sequelize(orm)


สรุป runserver

[nodejs] : runserver (commit 8-19)

เนื้อหาอธิบายใน BLOG

Commit 8 : Commit 8 Nodejs wordbucket blog
Commit 9 : Commit 9 Nodejs wordbucket blog
Commit 10 : Commit 10 Nodejs wordbucket blog
Commit 11 : Commit 11 Nodejs wordbucket blog
Commit 12 : Commit 12 Nodejs wordbucket blog
Commit 13 : Commit 13 Nodejs wordbucket blog
Commit 14 : Commit 14 Nodejs wordbucket blog
Commit 15 : Commit 15 Nodejs wordbucket blog
Commit 15(ต่อ) : Commit 15 Nodejs wordbucket blog (ต่อ)
Commit 16 : Commit 16 Nodejs wordbucket blog
Commit 17 : Commit 17 Nodejs wordbucket blog
Commit 18 : Commit 18 Nodejs wordbucket blog
Commit 19 : Commit 19 Nodejs wordbucket blog

LINK GITHUB EACH COMMIT

Commit 8 : install sequelize and sequelize-cli
Commit 9 : create sqlite3 database word and explanation table
Commit 10 : can add word to database
Commit 11 : can add word with explanation
Commit 12 : can add explanation in word view page
Commit 13 : first functional test and unit test try
Commit 14 : Add search function and produce the test until the current function
Commit 15 : add like, dislike, add new explanation to duplicate word on home page function
Commit 15(ต่อ) : add like, dislike, add new explanation to duplicate word on home page function (ต่อ)
Commit 16 : add browse function
Commit 17 : add export function
Commit 18 : add import function
Commit 19 : fix duplicate word and explanation in add_word, add_explanation, import_csv


http://b2-5720128.blogspot.com/2018/04/group-webapp-develop-wordbucket-commit.html

wordbucket

Assignment 1 : My webapp. < WordBucket > by django framework เป็นส่วนหนึ่งของวิชา Software Development Practice II ภาษาที่ไม่มีการนำไปใช้ในชีวิตประจำวันเราเรียกว่า “ภาษาที่ตายแล้ว” ปัจจุบันแต่ละวันที่คำศัพท์เกิดใหม่มากมาย เช่น
  • weeb A weeb (/wi b/) is a non-Japanese male who watches and is a fan of CGDCT anime, has a waifu, a waifu pillow and is obsessed with Japan. Credit
  • Jagoogala = just google it! ซึ่ง webapp. ที่อยากสร้างคือ word bank ให้ user มาแชร์ และ เก็บคำศัพท์ใหม่ๆที่เกิดขึ้นในปัจจุบัน

Features

  • Add word (with description)
  • Search (find word)
  • Browse (a-z)
  • Vote (useful or not)
  • login system
  • csv import/export
Domain name we thought “https://WordBucket.com/”

Django part

Model

4 classes
  • word
  • explanation
  • like
  • dislike

View

  • home_page : หน้า home page ของ webapp.
  • add_word : เพิ่มคำศัพท์พร้อมคำอธิบาย (ถ้าคำศัพท์ซ้ำจะโชว์ url ของคำศัพท์นั้นๆ เพื่อให้ user เข้าไปเพิ่ม คำอธิบาย ของตัวเองใน คำศัพท์ที่มีอยู่แล้วได้)
  • view_word : ดูในแต่คำมีคำอธิบายอะไรบ้าง
  • add_explanation : ใส่คำอธิบายเพิ่มในคำๆนั้น
  • vote_like : โหวตชอบ only user
  • vote_dislike : โหวตไม่ชอบ only user
  • search : ค้นหาคำศัพท์
  • import and export csv : อัพโหลด/ดาวโหลดไฟล์ csv เข้าสู่/จาก database

URL config

link urls.py ของ project กับ urls.py ของ app. name เหมือนชื่อ function ทุกอัน
  • home_page > '' (path)
  • add_word > 'add' (path)
  • view_word > r'^(\d+)/$' (repath)
  • add_explanation > r'^(\d+)/add_explanation$' (re_path)
  • vote_like > r'^(\d+)/vote_like$' (re_path)
  • vote_dislike > r'^(\d+)/vote_dislike$' (re_path)
  • search > r'^search/(.+)$' (re_path)
  • import_csv > r'^(\d+)/import$' (re_path)
  • export_csv > r'^(\d+)/export$' (re_path)
  • signup > r'^signup/$ (re_path)
  • login > r'^login/$' (re_path)
  • logout > r'^logout/$' (re_path)

Tests

functional test
unit test
now have 7 classes
  • HomePageTest
  • WordViewTest
  • NewWordTest
  • NewExplanationTest
  • VoteTest
  • AllAroundModelsTest
  • SearchAndBrowseTest

รายชื่อสมาชิกกลุ่ม

  • นาย ศุภณ์กัญจน์ สัตตพงศ์ 5701012620128 blog link
  • นาย ไอยคุปต์ อาภรณ์ศิริ 5701012630204 blog link
  • นาย ณฤดล ทรงอนุสรณ์ 5701012610122 blog link

nodejs To-Do apps

ทดสอบทำ To-Do app ตามใน website

https://medium.com/@atingenkay/creating-a-todo-app-with-node-js-express-8fa51f39b16f



อธิบายตามหน้า slide ซึ่งเพื่อนทำสอบขั้นต้นใน slide 3-7 ส่วนผมทำ slide 8 ขึ้นไป(ตาม link ข้างบน)

  • slide 3-4 : npm init เพื่อสร้าง package.js จะมี description ของ project ที่เราจะสร้าง
  • slide 5 : ทำการ install express
  • slide 6 : สร้าง localhost server แบบง่ายๆ(send 'Hello Node.js') ในการทดสอบโดยใช้ port 7777 และแสดงใน cmd ว่า Starting node.js on port 7777
  • slide 7 : รูปประกอบ slide 6

  • slide 8 : ทดสอบสร้าง localhost server แบบง่ายๆ(send 'Hello World!') ในการทดสอบโดยใช้ port 3000 และแสดงใน cmd ว่า 'Example app listening on port 3000!'
  • slide 9 : install ejs เพื่อใช้ template คล้าย django โดยสร้าง folder views ไว้เก็บ template และไฟล์ .html ต้องเปลี่ยนนามสกุลเป็น .ejs
  • slide 10 : สร้าง template แรกของเราโดย โดยมี input ไว้ใส่ task และ added task ไว้แสดง task ที่เรา add เข้าไป
  • slide 11 : สร้าง url path function แรกให้ render index.ejs
  • slide 12 : รูปประกอบ slide 11
  • slide 13 : สร้าง url path function ที่สองสำหรับ add task ในหน้า index.ejs
  • slide 14 : install body-parser สำหรับ stored เก็บค่าไว้ใน req-body object ขณะเปิด server ไว้ได้ (ถ้าปิด server ค่าใน req-body object จะหาย) เพื่อใช้เก็บ task และ request body object ในไฟล์ ejs
  • slide 15 : import body-parser แล้วสร้าง Array task สำหรับเก็บ task และ req-body object ค่าที่ post มา push มาลง array เมื่อเรียกใช้
  • slide 16 : เพิ่ม loop สำหรับใส่ check box สำหรับ task ไว้ติ๊กเพื่อย้าย task ไปใส่ complete task
  • slide 17 : รูปประกอบการทำงาน slide 16
  • slide 18 : ทดสอบใส่ task
  • slide 19 : สร้าง url path function ที่สองสำหรับ remove task และสร้าง Array complete สำหรับเก็บ task ที่กด remove มา(เสร็จแล้ว) จะ splice (ย้าย) จาก Array task มายัง Array complete
  • slide 20 : วนลูด show complete task ใน template index.ejs
  • slide 21 : ภาพทดสอบ run server
  • slide 22 : ทดสอบ remove task
slide 23 ขึ้นไปเป็นการทดสอบใช้ data แบบยังไม่ได้ใช้ sequelize ที่ทดสอบทำในสัปดาห์นั้น (แต่ผลออกมาไม่ค่อยดีนัก)

โดยการใช้งาน sequelize จะ update ใน commit ที่ 8 (link)



hello nodejs

ได้หาและทำตาม tutorial online โดย ยึดตาม website w3school ในส่วนที่เป็น nodejs (https://www.w3schools.com/nodejs/default.asp)

โดยทำตามไล่ตามลำดับดังภาพ



เช่น เป็น server localhost ที่ port 8080 โดย response คำว่า 'Hello World!'

Example

var http = require('http');

http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type''text/plain'});
    res.end('Hello World!');
}).listen(8080);
Run example »
แสดงคำบน command line

Example

console.log('This example is different!');
console.log('The result is displayed in the Command Line Interface');
Run example »

และอื่นๆตาม website ข้างต้น

Commit 17 Wordbucket : fixed duplicated upload, role, like or dislike spam(change model)

Assignment1 : Wordbucket GitHub Link

Commit 17 Wordbucket : fixed duplicated upload, role, like or dislike spam(change model)

Commits on Apr 1, 2018

unit test

- แก้ unit test ก่อนปรับ model จากอันเดิม ซึ่งจะอธิบายการเปลี่ยนแปลงข้างล่างนี้

@@ -3,7 +3,7 @@
from django.http import HttpRequest
from django.template.loader import render_to_string
from wordbucket.views import home_page
-from wordbucket.models import Word, Explanation, Like_and_dislike
+from wordbucket.models import Word, Explanation, Like, Dislike
class HomePageTest(TestCase):
@@ -71,27 +71,29 @@ def test_saving_and_retrieving_like_and_dislike(self):
explanation_.word = word_
explanation_.save()
- first_votes_like = Like_and_dislike()
- first_votes_like.votes_like = 1
+ first_votes_like = Like()
+ first_votes_like.user_like = '1'
first_votes_like.explanation = explanation_
first_votes_like.save()
- second_votes_dislike = Like_and_dislike()
- second_votes_dislike.votes_dislike = 2
+ second_votes_dislike = Dislike()
+ second_votes_dislike.user_dislike = '2'
second_votes_dislike.explanation = explanation_
second_votes_dislike.save()
saved_explanation = Explanation.objects.first()
self.assertEqual(saved_explanation, explanation_)
- saved_likes_and_dislikes = Like_and_dislike.objects.all()
- self.assertEqual(saved_likes_and_dislikes.count(), 2)
+ saved_likes = Like.objects.all()
+ self.assertEqual(saved_likes.count(), 1)
+ saved_dislikes = Dislike.objects.all()
+ self.assertEqual(saved_dislikes.count(), 1)
- first_saved_votes_like = saved_likes_and_dislikes[0]
- second_saved_votes_like = saved_likes_and_dislikes[1]
- self.assertEqual(first_saved_votes_like.votes_like, 1)
+ first_saved_votes_like = saved_likes[0]
+ second_saved_votes_like = saved_dislikes[0]
+ self.assertEqual(first_saved_votes_like.user_like, '1')
self.assertEqual(first_saved_votes_like.explanation, explanation_)
- self.assertEqual(second_saved_votes_like.votes_dislike, 2)
+ self.assertEqual(second_saved_votes_like.user_dislike, '2')
self.assertEqual(second_saved_votes_like.explanation, explanation_)
class WordViewTest(TestCase):
@@ -192,13 +194,9 @@ def test_redirects_like_to_word_view(self):
explanation_ = Explanation()
explanation_.explanation_text = 'test'
+ explanation_.votes_like += 1
explanation_.word = word_
explanation_.save()
-
- votes_like = Like_and_dislike()
- votes_like.votes_like = 0
- votes_like.explanation = explanation_
- votes_like.save()
response = self.client.get(
'/%d/like' % (explanation_.id,)
@@ -210,16 +208,12 @@ def test_redirects_dislike_to_word_view(self):
explanation_ = Explanation()
explanation_.explanation_text = 'test'
+ explanation_.votes_dislike += 1
explanation_.word = word_
explanation_.save()
-
- votes_dislike = Like_and_dislike()
- votes_dislike.votes_dislike = 0
- votes_dislike.explanation = explanation_
- votes_dislike.save()
response = self.client.get(
- '/%d/like' % (explanation_.id,)
+ '/%d/dislike' % (explanation_.id,)
)
self.assertRedirects(response, '/%d/' % (explanation_.id,))


models.py

- ปรับ model ย้าย votes_like และ vote_dislike มาเก็บใน explanation (จากคำแนะนำของอาจารย์ เรื่องตอนแรกที่จะแยก like ของ user และ anonymous เลยทำ model ออกมาในลักษณะแรก) จึงได้ปรับมาเป็น anonymouse ไม่สามารถ like dislike ได้ user เท่านั้นที่ like dislike ได้ โดยตาราง Like เก็บ user ที่ like ใน entity user_like โดยตาราง Dislike ก็ทำคล้ายกันแค่เก็บ user ที่ dislike ไว้ใน entity user_dislike

@@ -15,16 +15,27 @@ class Explanation(models.Model):
#foreign key
word = models.ForeignKey(Word, on_delete=models.CASCADE)
explanation_text = models.CharField(max_length=600)
+ votes_like = models.IntegerField(default=0)
+ votes_dislike = models.IntegerField(default=0)
def __str__(self):
return self.explanation_text
+ def __int__(self):
+ return self.votes_like
+ def __int__(self):
+ return self.votes_dislike
-class Like_and_dislike(models.Model):
+class Like(models.Model):
#foreign key for Like and Dislike
explanation = models.ForeignKey(Explanation, on_delete=models.CASCADE)
- #votes count
- votes_like = models.IntegerField(default=0)
- votes_dislike = models.IntegerField(default=0)
+ #user votes count
+ user_like = models.TextField(default='')
def __str__(self):
- return self.votes_like
+ return self.user_like
+
+class Dislike(models.Model):
+ #foreign key for Like and Dislike
+ explanation = models.ForeignKey(Explanation, on_delete=models.CASCADE)
+ #user votes count
+ user_dislike = models.TextField(default='')
def __str__(self):
- return self.votes_dislike
+ return self.user_dislike


admin.py

- เนืองจากเปลี่ยนแปลง model จึงแก้ admin ด้วยเผื่อใช้

@@ -1,10 +1,11 @@
from django.contrib import admin
-from .models import Word, Explanation, Like_and_dislike
+from .models import Word, Explanation, Like, Dislike
# Register your models here.
admin.site.register(Word)
admin.site.register(Explanation)
-admin.site.register(Like_and_dislike)
+admin.site.register(Like)
+admin.site.register(Dislike)

templates/detail.html

- เนื่องจากการเปลี่ยนแปลง จึงเตือน user ที่ไม่ logged inไว้ข้างบน
- แก้ตัวแปรในลูปให้ถูกต้อง(votes_like และ votes_dislike ถูกเปลี่ยนมาเก็บในตาราง Explanation)
- แก้ให้ admin เท่านั้นที่ใช้ import export ได้


@@ -9,7 +9,8 @@ <h1>Word Bucket</h1>
<p><a href="{% url 'wordbucket:logout' %}">logout</a></p>
{% else %}
You are not logged in
- <a href="{% url 'wordbucket:login' %}">LOGIN</a>
+ <a href="{% url 'wordbucket:login' %}">LOGIN</a><br>
+ You can not like and dislike.
<br>
{% endif %}
@@ -25,25 +26,26 @@ <h2>{{ word }}</h2>
<table id="id_explanation_table">
{% for explanation in word.explanation_set.all %}
<tr>
- <td>explanation {{ forloop.counter }} : {{ explanation.explanation_text }}
- {% for vote in explanation.like_and_dislike_set.all %}<br>
- &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+ <td>explanation {{ forloop.counter }} : {{ explanation.explanation_text }}<br>
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
- {{ vote.votes_like }} &nbsp;<a href="{% url 'wordbucket:like' explanation.id %}">LIKE</a>
- &nbsp; {{ vote.votes_dislike }} &nbsp;<a href="{% url 'wordbucket:dislike' explanation.id %}">DISLIKE</a>
- {% endfor %}<br><br>
+ {{ explanation.votes_like }} &nbsp;<a href="{% url 'wordbucket:like' explanation.id %}">LIKE</a>
+ &nbsp; {{ explanation.votes_dislike }} &nbsp;<a href="{% url 'wordbucket:dislike' explanation.id %}">DISLIKE</a>
+ <br><br>
</td>
</tr>
{% endfor %}
</table>
<br>
{% if user.is_authenticated %}
+ {% if user.get_username == 'admin' %}
<a href="{% url 'wordbucket:export' word.id %}">export</a><br><br>
<form action="{% url 'wordbucket:import' word.id %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
<input type="file" name="csv_file" />
<input type="submit" value="Upload" />
</form>
+ {% endif %}
{% endif %}
<a href="{% url 'wordbucket:home' %}">HOME</a>
</body>


views.py

- แก้ query/create ที่เกี่ยวกับ Like_and_dislike , votes_like , votes_dislike ทั้งหมด
- แก้ให้ไม่สามารถสร้าง word / explanation ที่เป็น "" (ช่องว่าง)ได้
- แก้ add_word และ add_explanation ให้ explanation ที่เหมือนกัน แต่คนละ word สามารถโพสได้
- แก้ vote_like และ vote_dislike ให้ปรับมาเป็น anonymouse ไม่สามารถ like dislike ได้ user เท่านั้นที่ like dislike ได้
- แก้ import csv ให้ไม่ข้ามไม่สร้าง explanation ที่ซ้ำกับที่มีอยู่แล้ว

@@ -2,7 +2,7 @@
from django.urls import reverse
from django.contrib.auth import login, authenticate
from django.contrib.auth.forms import UserCreationForm
-from wordbucket.models import Word, Explanation, Like_and_dislike
+from wordbucket.models import Word, Explanation, Like, Dislike
import string, csv, os, codecs
from django.http import HttpResponse
from io import TextIOWrapper
@@ -16,38 +16,44 @@ def home_page(request):
def view_word(request, word_id):
d_message = ""
word_ = Word.objects.get(id=word_id)
- return render(request, 'detail.html', {'word': word_, 'd_message': d_message})
+ like_ = Like.objects.all
+ dislike_ = Dislike.objects.all
+ return render(request, 'detail.html', {'word': word_, 'like': like_, 'dislike': dislike_, 'd_message': d_message})
def add_word(request):
d_message = ""
words = Word.objects.order_by('-date_pub')[:5]
word_reference = str(request.POST['word_input'])
+ explanation_reference = str(request.POST['explanation_input'])
# query for duplicate word
d_query = Word.objects.filter(word=word_reference)
- if not d_query :
+ if (not d_query) and (word_reference != '') :
word_ = Word.objects.create(word = request.POST['word_input'])
- explanation_ = Explanation.objects.create(explanation_text=request.POST['explanation_input'], word=word_)
- Like_and_dislike.objects.create(votes_like = 0, votes_dislike = 0, explanation = explanation_)
+ explanation_ = Explanation.objects.create(explanation_text=request.POST['explanation_input'], votes_like = 0, votes_dislike = 0, word=word_)
return redirect('/')
else :
- # duplacate word id
- dword_id = Word.objects.get(word = word_reference)
- explanation_ = Explanation.objects.create(explanation_text=request.POST['explanation_input'], word=dword_id)
- Like_and_dislike.objects.create(votes_like = 0, votes_dislike = 0, explanation = explanation_)
- d_message = "duplicate word, your explanation add to existing word."
- return render(request, 'home.html', {'words': words, 'd_message': d_message})
+ word_ = Word.objects.get(word=word_reference)
+ d_query2 = Explanation.objects.filter(explanation_text=explanation_reference, word=word_)
+ if (word_reference != '') and (not d_query2) :
+ # duplacate word id
+ dword_id = Word.objects.get(word = word_reference)
+ explanation_ = Explanation.objects.create(explanation_text=request.POST['explanation_input'], votes_like = 0, votes_dislike = 0, word=dword_id)
+ d_message = "duplicate word, your explanation add to existing word."
+ else :
+ d_message = "please enter the word"
+ alphabets = string.ascii_lowercase
+ return render(request, 'home.html', {'words': words, 'd_message': d_message, 'alphabets': alphabets})
def add_explanation(request, word_id):
word_ = Word.objects.get(id=word_id)
explanation_reference = str(request.POST['explanation_input'])
# query for duplicate explanation
- d_query = Explanation.objects.filter(explanation_text=explanation_reference)
- if not d_query :
- explanation_ = Explanation.objects.create(explanation_text=request.POST['explanation_input'], word=word_)
- Like_and_dislike.objects.create(votes_like = 0, votes_dislike = 0, explanation = explanation_)
+ d_query = Explanation.objects.filter(explanation_text=explanation_reference, word=word_)
+ if (not d_query) and (explanation_reference != '') :
+ explanation_ = Explanation.objects.create(explanation_text=request.POST['explanation_input'], votes_like = 0, votes_dislike = 0, word=word_)
return redirect('/%d/' % (word_.id,))
else :
- d_message = "duplicate explanation, please enter new explanation."
+ d_message = "duplicate or null explanation, please enter new explanation."
return render(request, 'detail.html', {'word': word_, 'd_message': d_message})
def search(request, word_search):
@@ -71,29 +77,28 @@ def search(request, word_search):
return render(request, 'search.html', {'word_found': word_found})
def vote_like(request, explanation_id):
- explanation_ = Explanation.objects.get(id=explanation_id)
- selected_explanation = explanation_.like_and_dislike_set.all()
- vote_like_ = selected_explanation.count()
- if vote_like_ == 0 :
- Like_and_dislike.objects.create(vote_like=1, explanation=explanation_)
- else :
- for voteslike in selected_explanation :
- voteslike.votes_like += 1
- voteslike.save()
- return HttpResponseRedirect(reverse('wordbucket:detail', args=(explanation_.word.id,)))
-
+ explanation_ = Explanation.objects.get(id=explanation_id)
+ if request.user.is_authenticated:
+ d_query = Like.objects.filter(user_like=request.user.username, explanation=explanation_)
+ if not d_query :
+ Like.objects.create(user_like=request.user.username, explanation=explanation_)
+ selected_explanation = explanation_.like_set.all()
+ vote_like_ = selected_explanation.count()
+ explanation_.votes_like = vote_like_
+ explanation_.save()
+ return HttpResponseRedirect(reverse('wordbucket:detail', args=(explanation_.word.id,)))
def vote_dislike(request, explanation_id):
- explanation_ = Explanation.objects.get(id=explanation_id)
- selected_explanation = explanation_.like_and_dislike_set.all()
- vote_dislike_ = selected_explanation.count()
- if vote_dislike_ == 0 :
- Like_and_dislike.objects.create(vote_dislike=1, explanation=explanation_)
- else :
- for votesdislike in selected_explanation :
- votesdislike.votes_dislike += 1
- votesdislike.save()
- return HttpResponseRedirect(reverse('wordbucket:detail', args=(explanation_.word.id,)))
+ explanation_ = Explanation.objects.get(id=explanation_id)
+ if request.user.is_authenticated:
+ d_query = Dislike.objects.filter(user_dislike=request.user.username, explanation=explanation_)
+ if not d_query :
+ Dislike.objects.create(user_dislike=request.user.username, explanation=explanation_)
+ selected_explanation = explanation_.dislike_set.all()
+ vote_dislike_ = selected_explanation.count()
+ explanation_.votes_dislike = vote_dislike_
+ explanation_.save()
+ return HttpResponseRedirect(reverse('wordbucket:detail', args=(explanation_.word.id,)))
# auth. part
@@ -136,6 +141,8 @@ def import_csv(request, word_id):
reader = csv.reader(csvfile)
next(reader)
for row in reader:
- explanation_ = Explanation.objects.create(explanation_text=row[1], word=word_)
- Like_and_dislike.objects.create(votes_like = 0, votes_dislike = 0, explanation = explanation_)
+ #duplicate explanation
+ d_query = Explanation.objects.filter(explanation_text=row[1], word=word_)
+ if (not d_query) and (row[1] != '') :
+ explanation_ = Explanation.objects.create(explanation_text=row[1], votes_like = 0, votes_dislike = 0, word=word_)
return HttpResponseRedirect(reverse('wordbucket:detail', args=(word_id,)))