Skip to content
Snippets Groups Projects
components.py 6.7 KiB
Newer Older
Woojong Nam's avatar
Woojong Nam committed
#!/usr/bin/env python3

""" 
	This module provides a set of reuseable functions
	which provide the main app functionality.
	We could even go a stage further and separate
	database interactions from logic...
"""

# import modules from Python standard library
from bson.objectid import ObjectId
from datetime import datetime
import bcrypt

def login( db, form ):
	""" Logs a user in if the Username and Password
	    submitted in a form match a record in database

	Params:
		db: 	handle to a database
		form: 	FieldStorage object
	
	Returns:
		Dict 
	"""
	# login status is false by default
	status = False
	try:
		# get the username from the form
		username = form['username'].value
		# get the unhashed password sent in the form
		pw = form['password'].value
	except KeyError:
		# a field must be missing from the form data
		response = "I don't think you filled all the fields in..."
	else:
		# look for an account with the username
		account = db.accounts.find_one({
			"username":username,
			})
		# check if an account came back 
		if account is not None:
			# compare the unhashed password from the form
			# with the hashed version in the database
			if bcrypt.checkpw( pw.encode('utf-8'), account['password'].encode('utf-8') ):
				# if they match, output a personal greeting
				# was this a DP student?
				if account['username'] == "Sunday":
					response = "What a good kitty. Now fill in the <a href='https://moduleevaluation.gold.ac.uk/surveyPrivacyStatement/Kf4iWgP74kSxYCxRA'>module feedback form</a> and you shall have some pie."
				else:
					response = "Hello {}!".format( account['name']['first'] )
					status = True
			else:
				# if not, we know it was a wrong password
				response = "Wrong password."
		else:
			# their username didn't match, so tell them that
			response = "Sorry, I don't think I know you!"
	# return a dict with the result and a message
	return {"status":status,"msg":response}

def register_account( db, form ):
	""" Registers a new user by inserting an account
	    in the accounts collection

	Params:
		db: 	handle to a database
		form: 	FieldStorage object
	
	Returns:
		Dict 
	"""
	# registration status false by default
	status = False
	try:
		# get the username from the form
		username = form['username'].value
		# get the email from the form
		email = form['email'].value
		# get the unhashed password from the form
		password = form['password'].value
	except KeyError:
		# a field must be missing from the form data
		response = "I don't think you filled all the fields in..."
	else:
		# check if there is already an account with the email
		# or username
		result = db.accounts.find_one( { '$or': [
			{ 'username' : username },
			{ 'email': email }
		]} )
		# if so, send back a useful response
		if result:
			if email in result.values():
				response = "Email address already registered."
			else:
				response = "Username taken."
		# otherwise go ahead and make the account
		else:
			hashed_pw = bcrypt.hashpw( password.encode('utf-8'), bcrypt.gensalt() )
			query = {
				"username": username,
				"password": hashed_pw.decode('utf-8'),
				"is_admin": 0,
				"created" : datetime.now().timestamp()
			}
			# make sure name fields aren't empty
			# if they are, give them empty string values
			if 'first' in form:
				first = form['first'].value
			else:
				first = ""
			if 'last' in form:
				last = form['last'].value
			else:
				last = ""
			# append name to query
			query["name"] = { "first": first, "last": last }
			# insert the account in the database
			doc = db.accounts.insert( query )
			if doc:
				status = True
				response = "New account registered. <a href='/cgi-bin/splash.py' title='login'>Click here</a> to go Log in!"
			else:
				response = "Problem registering your account. Please refresh the page to try again."
	# return something useful...
	return {'status': status, 'msg': response}

def latest_fluck( db ):
	""" Returns details of the most recently flucked
	    cat in the database

	Params:
		db: 	handle to a database
	
	Returns:
		Dict 
	"""
	# start with an empty dict
	image = {}
	# get details of last flucked cat
	result = db.flucks.find({"is_flucked":1}).sort([('timestamp', -1)]).limit(1)
	# get the associated image
	for fluck in result:
		result2 = db.images.find({"_id":fluck["image_id"]})
	# if a doc was returned, put the bits we want in the image dict
	for doc in result2:
		image = {"url":doc["url"],"alt":doc["alt"]}
	return image

def most_flucked( db ):
	""" Returns details of the most flucked
	    cat in the database

	Params:
		db: 	handle to a database
	
	Returns:
		Dict 
	"""
	# start with an empty dict
	image = {}
	# define a pipeline of operations to get most flucked image id
	# from flucks collection
	pipeline = [
		{"$group": {"_id": "$image_id", "count": {"$sum": 1}}},
		{"$sort": {"count":-1}},
		{"$limit":1}
		]

	# apply pipeline of operations using aggregate
	result = db.flucks.aggregate( pipeline )

	# check if a result was returned
	if result:
		for fluck in result:
			# get the associated image document
			doc = db.images.find_one({"_id": fluck["_id"]})

	# if a doc was returned, put the bits we want in the image dict
	if doc:
		image = {"url":doc["url"],"alt":doc["alt"]}
	return image

def random_cat( db ):
	""" Returns details of a random cat in the database

	Params:
		db: 	handle to a database
	
	Returns:
		Dict 
	"""
	# start with an empty dict
	image = {}
	# get a random sample from images collection of size '1'
	result = db.images.aggregate(
		[{ '$sample': { 'size': 1 } }]
	)
	# if a result came back, do stuff with it...
	if result:
		# iterate through objects in the cursor (should only be 1)
		for doc in result:
			# put the bits we want in the image dict
			image = {
				'src': doc['url'],
				'alt': doc['alt'],
				'id': doc['_id']
			}
	return image

def count_flucks( db, img_id ):
	""" Returns details of a random cat in the database

	Params:
		db: 	handle to a database
		img_id:	object id of an image document
	
	Returns:
		Dict 
	"""
	# try to count flucks associated with image id
	try:
		num_flucks = db.flucks.find( {"image_id": img_id, "is_flucked":1} ).count()
	# if img_id invalid, return false rather than crashing
	except NameError:
		return False
	else:
		return num_flucks

def insert_fluck( db, img_id, is_flucked ):
	""" Inserts new fluck in flucks collection
	    based on form data

	Params:
		db: 	handle to a database
		form: 	FieldStorage object
	
	Returns:
		Bool 
	"""
	# try to insert a fluck
	query =	{ 
			"image_id": ObjectId(img_id),
			"is_flucked":is_flucked,
			"timestamp":datetime.now().timestamp() 
		} 
	try:
		result = db.flucks.insert( query )
	# if img_id invalid, return something useful
	except NameError:
		return {'status': False, 'query': query}
	# else return info about the inserted document
	else:
		return {'status': True, 'fluck_id': result}