<repo_name>DreamN/Multiuserblog<file_sep>/models.py
from google.appengine.ext import db


class Post(db.Model):
    """Post Model"""
    subject = db.StringProperty(required=True)
    author = db.StringProperty(required=True)
    content = db.TextProperty(required=True)
    created = db.DateTimeProperty(auto_now_add=True)


class Comment(db.Model):
    """Comment Model"""
    post = db.StringProperty(required=True)
    user = db.StringProperty(required=True)
    content = db.TextProperty(required=True)
    created = db.DateTimeProperty(auto_now_add=True)


class Like(db.Model):
    """Like Model"""
    post = db.StringProperty(required=True)
    user = db.StringProperty(required=True)
<file_sep>/README.md
# Multi User Blog
## This multi user blog web app is a project submitted to Udacity's nanodegrees full stack developer course

**To run website*
- Clone [Multi User Blog](https://github.com/DreamN/Multiuserblog) (This Project)
```
	$git clone https://github.com/DreamN/Multiuserblog.git
```
- Install Python 2
- Install [Google App Engine SDK](https://cloud.google.com/appengine/downloads#Google_App_Engine_SDK_for_Python)
- run website(when developing)
```
 dev_appserver.py .
```
- run website(Deploy)
```
 gcloud app deploy
```

## Features
- Signup, Login and Logout (User account)
- Change password and email (User account)
- Add, Edit, Delete Post (Users only be able to edit/delete their own posts)
- Like/Unlike and Comment Post!
- Profile for user
- storing passwords by hashing
- edit/delete comment<file_sep>/auth.py
import webapp2
import random
import hashlib
from string import letters
from google.appengine.ext import db
import blog


def make_salt(length=5):
    """make salt for hashing"""
    return ''.join(random.choice(letters) for x in xrange(length))


def make_pw_hash(name, pw, salt=None):
    """make hashed password"""
    if not salt:
        salt = make_salt()
    h = hashlib.sha256(name + pw + salt).hexdigest()
    return '%s,%s' % (salt, h)


def valid_pw(name, password, h):
    """password validation"""
    salt = h.split(',')[0]
    return h == make_pw_hash(name, password, salt)


def users_key(group='default'):
    """user key"""
    return db.Key.from_path('users', group)


class User(db.Model):
    """User Model"""
    username = db.StringProperty(required=True)
    password = db.StringProperty(required=True)
    email = db.StringProperty(required=False)

    @classmethod
    def by_id(cls, uid):
        """Get user by ID"""
        return User.get_by_id(uid, parent=users_key())

    @classmethod
    def by_username(cls, username):
        """Get user by Username"""
        u = User.all().filter('username =', username).get()
        return u

    @classmethod
    def register(cls, username, password, email=None):
        """Register User"""
        pw_hash = make_pw_hash(username, password)
        return User(parent=users_key(),
                    username=username,
                    password=<PASSWORD>,
                    email=email)

    @classmethod
    def login(cls, username, password):
        """Login User"""
        u = cls.by_username(username)
        if u and valid_pw(username, password, u.password):
            return u

    @classmethod
    def set_password(cls, username, old_password, new_password):
        """Set New Password for User"""
        u = cls.by_username(username)
        if u and valid_pw(username, old_password, u.password):
            pw_hash = make_pw_hash(username, new_password)
            u.password = <PASSWORD>
            return u
        else:
            return False
<file_sep>/blog.py
import auth
import os
import webapp2
import jinja2
import re
import hmac
from string import letters
from pprint import pprint
from google.appengine.ext import db
from models import Post, Comment, Like

SECRET = 'MayTheForceBeWithYou'
BLOG_TITLE = 'This is my blog'
POST_LIMIT = 10  # Post display in 'home' page limit

template_dir = os.path.join(os.path.dirname(__file__), 'templates')
jinja_env = jinja2.Environment(loader=jinja2.FileSystemLoader(template_dir),
                               autoescape=True)

USER_RE = re.compile(r"^[a-zA-Z0-9_-]{3,20}$")


def valid_username(username):
    return username and USER_RE.match(username)

PASS_RE = re.compile(r"^.{3,20}$")


def valid_password(password):
    return password and PASS_RE.match(password)

EMAIL_RE = re.compile(r'^[\S]+@[\S]+\.[\S]+$')


def valid_email(email):
    return not email or EMAIL_RE.match(email)


def render_str(template, **params):
    t = jinja_env.get_template(template)
    return t.render(params)


def make_secure_val(val):
    return '%s|%s' % (val, hmac.new(SECRET, val).hexdigest)


def check_secure_val(secure_val):
    val = secure_val.split('|')
    val2 = make_secure_val(val[0]).split('|')
    if val[0] == val2[0]:
        return val[0]


class BaseHandler(webapp2.RequestHandler):

    def render(self, template, **kw):
        kw['blog_title'] = BLOG_TITLE
        if self.user:
            kw['user'] = self.user
        else:
            kw['user'] = False
        self.response.out.write(render_str(template, **kw))

    def write(self, *a, **kw):
        self.response.out.write(*a, **kw)

    def set_secure_cookie(self, name, val):
        cookie_val = make_secure_val(val)
        self.response.headers.add_header(
            'Set-Cookie',
            '%s=%s; Path=/' % (name, cookie_val))

    def read_secure_cookie(self, name):
        cookie_val = self.request.cookies.get(name)
        return cookie_val and check_secure_val(cookie_val)

    def login(self, user):
        self.set_secure_cookie('user_id', str(user.key().id()))

    def logout(self):
        self.response.headers.add_header('Set-Cookie', 'user_id=; Path=/')

    def login_required(self):
        if not self.user:
            self.redirect('/login')
            return False
        return True

    def initialize(self, *a, **kw):
        webapp2.RequestHandler.initialize(self, *a, **kw)
        uid = self.read_secure_cookie('user_id')
        self.user = uid and auth.User.by_id(int(uid))

# <<<----Views---->>>


class Welcome(BaseHandler):

    """Welcome Page of Blog"""

    def render_front(self):
        posts = db.GqlQuery("SELECT * FROM Post "
                            "ORDER BY created DESC")[:POST_LIMIT]
        self.render('index.html', posts=posts)

    def get(self):
        self.render_front()


class SignUp(BaseHandler):

    """Signup Page"""

    def get(self):
        self.render('signup.html')

    def post(self):
        self.username = self.request.get('username')
        self.password = self.request.get('<PASSWORD>')
        self.verify = self.request.get('verify')
        self.email = self.request.get('email')

        have_error = False
        params = {'username': self.username, 'password': <PASSWORD>}

        if not valid_username(self.username):
            params['error_username'] = "That's not a valid username."
            have_error = True

        if not valid_password(self.password):
            params['error_password'] = "That wasn't a valid password."
            have_error = True

        elif self.password != self.verify:
            params['error_verify'] = "Your passwords didn't match."
            have_error = True

        if not valid_email(self.email):
            params['error_email'] = "That's not a valid email."
            have_error = True

        if have_error:
            self.render('signup.html', **params)
        else:
            self.done()

    def done(self):
        # make sure the user doesn't already exist
        u = auth.User.by_username(self.username)
        if u:
            msg = 'That user already exists.'
            self.render('signup.html', error_username=msg)
        else:
            u = auth.User.register(self.username, self.password, self.email)
            u.put()

            self.login(u)
            self.redirect('/profile')


class Login(BaseHandler):

    """Login Page"""

    def get(self):
        self.render('login.html')

    def post(self):
        username = self.request.get('username')
        password = self.request.get('password')
        u = auth.User.login(username, password)
        if u:
            self.login(u)
            self.redirect('/')
        else:
            msg = 'Invalid login'
            self.render('login.html', error_message=msg)


class Logout(BaseHandler):

    """Logout and Redirect to Signup page"""

    def get(self):
        self.logout()
        self.redirect('/signup')


class NewPost(BaseHandler):

    """New Post Page (For Create a new post)"""

    def get(self):
        if self.login_required():
            self.render('newpost.html')

    def post(self):
        if self.login_required():
            subject = self.request.get('subject')
            content = self.request.get('content')
            if subject and content:
                p = Post(subject=subject, author=self.user.username,
                         content=content)
                p.put()
                p_id = p.key().id()
                self.redirect('/post/'+str(p_id))
            else:
                error_message = 'both subject and content is required!'
                self.render('newpost.html', error_message=error_message,
                            subject=subject,
                            content=content)


class PostPage(BaseHandler):

    """Page of each post"""

    def get(self, post_id=""):
        p = Post.get_by_id(int(post_id))
        comments = Comment.all().filter('post = ', post_id)
        comments = sorted(comments, key=lambda x: x.created, reverse=True)
        like = Like.all().filter('post = ', post_id)
        like_amount = like.count()
        if self.user:
            like_able = not p.author == self.user.username
            liked = like.filter('user = ', self.user.username).get()
            self.render('post.html', post=p,
                        editable=not like_able,
                        comments=comments,
                        liked=liked,
                        like_able=like_able,
                        like_amount=like_amount)
        else:
            self.render('post.html', post=p,
                        comments=comments)

    def post(self, post_id=""):
        if self.login_required():
            if 'like_post' in str(self.request):
                # Like or unlike
                like_post = self.request.get('like_post')
                p = Post.get_by_id(int(post_id))
                if not p.author == self.user.username:
                    if like_post == 'Like':
                        l = Like(post=post_id, user=self.user.username)
                        l.put()
                    elif like_post == 'Unlike':
                        try:
                            l = Like.all().filter('post = ', post_id)
                            l = l.filter('user = ', self.user.username).get()
                            l.delete()
                        except:
                            print('Like isn\'t exist')
                else:
                    self.write('You can\'t Like\\Unlike your own post')
            else:
                # Comment
                p = Post.get_by_id(int(post_id))
                content = self.request.get('comment')
                comment = Comment(post=post_id,
                                  user=self.user.username,
                                  content=content)
                comment.put()
            self.redirect('/post/'+post_id)


class Profile(BaseHandler):

    """User's Profile Page (Detail, user's post)"""

    def get(self):
        if self.login_required():
            p = Post.all().filter('author = ', self.user.username)
            self.render('profile.html', profile_user=self.user,
                        posts=p)


class EditPage(BaseHandler):

    """Page that display when edit post"""

    def get(self, post_id=""):
        if self.login_required():
            p = Post.get_by_id(int(post_id))
            if p.author == self.user.username:
                self.render('newpost.html', subject=p.subject,
                            content=p.content,
                            edit=True,
                            post_id=post_id)
            else:
                self.render('permissionerror.html', item=p.subject)

    def post(self, post_id=""):
        if self.login_required():
            p = Post.get_by_id(int(post_id))
            subject = self.request.get('subject')
            content = self.request.get('content')

            if p.author == self.user.username:
                if subject and content:
                    p.subject = subject
                    p.content = content
                    p.put()
                    self.redirect('/post/'+post_id)
                else:
                    error_message = 'both subject and content is required!'
                    self.render('newpost.html', error_message=error_message,
                                subject=subject,
                                content=content)
            else:
                self.render('permissionerror.html', item=p.subject)


class DeletePage(BaseHandler):

    """Delete Post Comfirmation Page"""

    def get(self, post_id=""):
        if self.login_required():
            p = Post.get_by_id(int(post_id))
            if p.author == self.user.username:
                self.render('deletepage.html', item=p.subject,
                            post_id=post_id)
            else:
                self.render('permissionerror.html', item=p.subject)

    def post(self, post_id=""):
        if self.login_required():
            confirm = self.request.get('confirm')
            if confirm == 'Delete':
                p = Post.get_by_id(int(post_id))
                p.delete()
                self.redirect('/')


class EditComment(BaseHandler):

    """Page that display when edit comment"""

    def get(self, comment_id=""):
        if self.login_required():
            c = Comment.get_by_id(int(comment_id))
            if c.user == self.user.username:
                self.render('editcomment.html', content=c.content,
                            post_id=c.post)
            else:
                self.render('permissionerror.html', item='this comment')

    def post(self, comment_id=""):
        if self.login_required():
            c = Comment.get_by_id(int(comment_id))
            content = self.request.get('content')

            if c.user == self.user.username:
                if content:
                    c.content = content
                    c.put()
                    self.redirect('/post/'+c.post)
                else:
                    er_msg = 'Comment\'s content is required'
                    self.render('editcomment.html', error_message=er_msg,
                                content=content)
            else:
                self.render('permissionerror.html', item='this comment')


class DeleteComment(BaseHandler):

    """Delete Comment Confirmation Page"""

    def get(self, comment_id=""):
        if self.login_required():
            c = Comment.get_by_id(int(comment_id))
            if c.user == self.user.username:
                self.render('deletepage.html', item='this comment',
                            post_id=c.post)
            else:
                self.redirect('/post/'+c.post)

    def post(self, comment_id=""):
        if self.login_required():
            confirm = self.request.get('confirm')
        if confirm == 'Delete':
            c = Comment.get_by_id(int(comment_id))
            p = c.post
            c.delete()
            self.redirect('/post/'+p)


class Setting(BaseHandler):

    """User's Setting Page (able to change email and password)"""

    def get(self):
        if self.login_required():
            self.render('setting.html', email=self.user.email)

    def post(self):
        if self.login_required():
            email = self.request.get('email')
            old_password = self.request.get('old_password')
            password = self.request.get('password')
            verify = self.request.get('verify')
            u = self.user
            have_error = False
            params = {'email': email}

            if not valid_email(email):
                params['error_email'] = "That's not a valid email."
                have_error = True

            if not valid_password(password):
                params['error_password'] = "That wasn't a valid password."
                have_error = True

            if password != verify:
                params['error_verify'] = "Your passwords didn't match."
                have_error = True

            u_new = auth.User.set_password(u.username, old_password, password)
            if not u_new:
                params['error_old_password'] = "Your old password is invalid"
                have_error = True

            if not have_error:
                u_new.email = email
                u_new.put()
                self.redirect('/profile')

            else:
                self.render('setting.html', **params)


app = webapp2.WSGIApplication([(r'/', Welcome),
                               (r'/newpost', NewPost),
                               (r'/post/(\d+)', PostPage),
                               (r'/edit/(\d+)', EditPage),
                               (r'/delete/(\d+)', DeletePage),
                               (r'/edit-comment/(\d+)', EditComment),
                               (r'/delete-comment/(\d+)', DeleteComment),
                               (r'/profile', Profile),
                               (r'/setting', Setting),
                               (r'/signup', SignUp),
                               (r'/login', Login),
                               (r'/logout', Logout)],
                              debug=True)
