From f0133dc8b2cee870d1b862b3a86738685a164866 Mon Sep 17 00:00:00 2001 From: Paul Ferrell Date: Fri, 2 Sep 2011 01:55:34 -0500 Subject: [PATCH] File submission and user management works. Holy cow\! --- .gitignore | 1 + basic_site/__init__.py | 10 ++ basic_site/models.py | 44 +++++-- basic_site/templates/files.mako | 12 ++ basic_site/templates/head.mako | 31 +++-- basic_site/templates/main.mako | 8 +- basic_site/templates/users.mako | 56 ++++----- basic_site/views.py | 207 ++++++++++++++++++++++++-------- development.ini | 11 +- 9 files changed, 267 insertions(+), 113 deletions(-) create mode 100644 basic_site/templates/files.mako diff --git a/.gitignore b/.gitignore index c9b568f..053c8ce 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.pyc *.swp +basic_site.db diff --git a/basic_site/__init__.py b/basic_site/__init__.py index 94a730c..7ce4d62 100644 --- a/basic_site/__init__.py +++ b/basic_site/__init__.py @@ -31,5 +31,15 @@ def main(global_config, **settings): config.add_view('basic_site.views.home', route_name='home', renderer='basic_site:templates/main.mako') + config.add_route('users', '/users') + config.add_view('basic_site.views.users', route_name='users', + renderer='basic_site:templates/users.mako') + config.add_route('file', '/file/{name}*rev') + config.add_view('basic_site.views.file', route_name='file') + config.add_route('files', '/files/') + config.add_view('basic_site.views.files', route_name='files', + renderer='basic_site:templates/files.mako') + config.add_route('logout', '/logout/') + config.add_view('basic_site.views.logout', route_name='logout') return config.make_wsgi_app() diff --git a/basic_site/models.py b/basic_site/models.py index 0296db1..ef0d21e 100644 --- a/basic_site/models.py +++ b/basic_site/models.py @@ -1,25 +1,36 @@ +import datetime + from sqlalchemy import create_engine, Column, ForeignKey -from sqlalchemy.orm import sessionmaker, scoped_session from sqlalchemy.ext.declarative import declarative_base +import sqlalchemy.orm +from sqlalchemy.orm import sessionmaker, scoped_session from sqlalchemy.types import String, DateTime, Integer, Boolean +from pyramid.security import Allow, Everyone +import transaction + from zope.sqlalchemy import ZopeTransactionExtension from z3c.bcrypt import BcryptPasswordManager manager = BcryptPasswordManager() -from pyramid.security import Allow, Everyone - -import sqlalchemy.orm - DBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension())) Base = declarative_base() +DEFAULT_ADMIN_PW = 'change_this!' def initialize_sql(engine): DBSession.configure(bind=engine) Base.metadata.bind = engine Base.metadata.create_all(engine) + session = DBSession() + admin = session.query(User).get('admin') + if not admin: + admin = User('admin', DEFAULT_ADMIN_PW, True, 'Admin') + session.add(admin) + session.flush() + transaction.commit() + class RootFactory(object): __acl__ = [ (Allow, Everyone, 'view'), (Allow, 'group:editors', 'edit'), @@ -28,7 +39,7 @@ def __init__(self, request): pass class User(Base): - __tablename__ = 'Users' + __tablename__ = 'users' uid = Column(String(10), primary_key=True) pw_hash = Column(String(), nullable=False) admin = Column(Boolean(), nullable=False) @@ -49,12 +60,10 @@ def check_pw(self, passwd): return manager.checkPassword(self.pw_hash, passwd) def change_pw(self, new): - """Verifies the old pw before changing it to new. Returns True if - successful.""" - self.pw_hash = manager.encodePassword(pw) + self.pw_hash = manager.encodePassword(new) class Post(Base): - __tablename__ = 'Post' + __tablename__ = 'posts' id = Column(Integer(), primary_key=True) created = Column(DateTime(), nullable=False) creator = Column(String(10), nullable=False) @@ -113,7 +122,7 @@ def restore(self, user): session.flush() class Page(Base): - __tablename__ = 'Page' + __tablename__ = 'pages' id = Column(Integer(), primary_key=True) name = Column(String(15), unique=True) created = Column(DateTime(), nullable=False) @@ -170,3 +179,16 @@ def restore(self, user): page = Page(user, self.name, self.contents, self.created) session.add(page) session.flush() + +class File(Base): + __tablename__ = 'files'; + id = Column(Integer, primary_key=True) + name = Column(String, nullable=False) + submitter = Column(String, nullable=False) + changed = Column(DateTime, nullable=False) + size = Column(Integer) + + def __init__(self, name, submitter): + self.name = name + self.submitter = submitter + self.changed = datetime.datetime.now() diff --git a/basic_site/templates/files.mako b/basic_site/templates/files.mako new file mode 100644 index 0000000..ced8d8e --- /dev/null +++ b/basic_site/templates/files.mako @@ -0,0 +1,12 @@ +<%include file="head.mako" /> + +
+
+ + + + +
+ +<%include file="foot.mako" /> diff --git a/basic_site/templates/head.mako b/basic_site/templates/head.mako index 2abbf53..521c357 100644 --- a/basic_site/templates/head.mako +++ b/basic_site/templates/head.mako @@ -1,23 +1,23 @@ - ${request.registry.settings['site_name'] + page.subtitle|h} + ${request.registry.settings['site_name'] + page_subtitle|h} @@ -30,15 +30,20 @@ return '' %>
-
-% if 'message' in request.params: -
${request.params['message']|h}
-% elif message: -
${message|h}
+% if message: +
${message|h}
% endif diff --git a/basic_site/templates/main.mako b/basic_site/templates/main.mako index 05ca1dd..aa99a8c 100644 --- a/basic_site/templates/main.mako +++ b/basic_site/templates/main.mako @@ -1,12 +1,12 @@ <%include file="head.mako" />
-% for item in news: +% for post in posts:
- ${item.content} + ${post.content}
% endfor diff --git a/basic_site/templates/users.mako b/basic_site/templates/users.mako index 2ca6aa4..2e5e6c3 100644 --- a/basic_site/templates/users.mako +++ b/basic_site/templates/users.mako @@ -2,11 +2,10 @@
-% if current_user.admin: +% if user.admin:

Add User:

-
+ @@ -17,46 +16,49 @@ - +
%endif

Change your password:

-
+ - +
- -
UserFull NameAdmin - % for user in users: -
${user.uid}${user.fullname} - % if current_user.admin: - <% - toggle_href = request.route_url('mod_user', action='toggle_admin', - uid=user.uid) - delete_href = request.route_url('mod_user', action='delete', - uid=user.uid) - is_admin = 'Yes' if user.admin else 'No' - %> - ${is_admin} - delete - % else: - ${'Yes' if user.admin else 'No'} - % endif - % endfor -
+% if user.admin: +
+% endif + + ${' + % if user.admin: +
' if user.admin else ''|n}UserFull NameAdmin + % for e_user in users: +
+ % endif + ${e_user.uid}${e_user.fullname} + ${'yes' if e_user.admin else 'no'} + % endfor + % if user.admin: +
For selected user: + + + % endif +
+% if user.admin: +
+% endif
diff --git a/basic_site/views.py b/basic_site/views.py index 96f1565..63efb10 100644 --- a/basic_site/views.py +++ b/basic_site/views.py @@ -1,17 +1,34 @@ -from basic_site.models import DBSession -from basic_site.models import Page, Post +from basic_site.models import DBSession, DEFAULT_ADMIN_PW +from basic_site.models import Page, Post, File, User from basic_site.security import groupfinder, login -import sqlalchemy.orm +import os from pyramid.httpexceptions import HTTPFound +from pyramid.exceptions import NotFound from pyramid.security import forget from pyramid.url import route_url +import sqlalchemy.orm +import transaction + def get_context(request): """Get the basic values all contexts should have, including info on the logged in user and a list of pages.""" - context = get_context(request) + + context = {} + user, msg = login(request) + if user is None and msg is None: + session = DBSession() + admin = session.query(User).get('admin') + if admin.check_pw(DEFAULT_ADMIN_PW): + user = admin + msg = "The default admin password is: '%s'. CHANGE IT! Until it "\ + "is changed, all visitors are automatically admin." % \ + DEFAULT_ADMIN_PW + + context['user'] = user + context['message'] = msg session = DBSession() menu_pages = session.query(Page.id, Page.name)\ @@ -251,23 +268,18 @@ def history(request): return context def users(request): - """Give a list of users.""" - - session = DBSession() - - context = get_context(request) - - context['users'] = session.query(User).order_by(User.uid).all() - return context - -def mod_users(request): """This view handles adding, deleting, and editing users.""" session = DBSession() context = get_context(request) - action = request.matchdict['action'] + message = '' + + if 'action' in request.POST: + action = request.POST.getone('action') + else: + action = None if action == 'add': n_uid = request.POST.getone('uid') @@ -284,36 +296,11 @@ def mod_users(request): user = User(n_uid, passwd, admin, fullname) session.add(user) message = "Added user %s" % user.uid + #transaction.commit() except ValueError, msg: message = str(msg) - else: - e_uid = request.matchdict['uid'] - user = session.query(User).get(e_uid) - if not user: - message = "User %s does not exist." % e_uid - elif action == 'delete': - if uid == 'admin': - message = "Cannot delete the admin user." - else: - session.delete(user) - message = "User %s deleted." % e_uid - elif action == 'toggle_admin': - user.admin = not user.admin - session.flush() - message = "User %s admin priviliges %s" %\ - (e_uid, 'granted' if user.admin else 'revoked') - - request.GET['message'] = message - return HTTPFound(location=request.route_url('users'), - headers=request.headers) - -def change_pw(request): - session = DBSession() - - context = get_context(request) - - c_uid = request.matchdict['c_uid'] - if user and c_uid == user.uid: + elif action == 'change_pw': + user = context['user'] old = request.POST.getone('old') new = request.POST.getone('new') repeat = request.POST.getone('repeat') @@ -324,24 +311,138 @@ def change_pw(request): else: user.change_pw(new) message = "Password changed" - else: - message = "No such user." + session.add(user) - request.GET['message'] = message - return HTTPFound(location=request.route_url('users'), - headers=request.headers) + elif action is not None: + e_uid = request.POST.getone('e_uid') + user = session.query(User).get(e_uid) + if not user: + message = "User %s does not exist." % e_uid + elif action == 'delete': + if user.uid == 'admin': + message = "Cannot delete the admin user." + else: + session.delete(user) + message = "User %s deleted." % e_uid + #transaction.commit() + elif action == 'toggle_admin': + if user.uid == 'admin': + message = "The admin user is always an admin." + else: + user.admin = not user.admin + session.flush() + message = "User %s admin priviliges %s" %\ + (e_uid, 'granted' if user.admin else 'revoked') + #transaction.commit() + + if message: + context['message'] = message + context['users'] = session.query(User).order_by(User.uid).all() + context['page_name'] = '*Users' + context['page_subtitle'] = 'Manage Users' + return context def logout(request): headers = forget(request) return HTTPFound(location=request.route_url('home'), - headers=reqeust.headers) + headers=headers) + def file(request): session = DBSession() response = request.response - file_id = request.matchdict['file_id'] - file_info = session.query(File).get(file_id) + name = request.matchdict['name'] + rev_tuple = request.matchdict['rev'] + try: + if len(rev_tuple) == 1: + rev = int(rev_tuple[0]) + elif len(rev_tuple) > 1: + raise ValueError + else: + rev = None + except ValueError: + raise NotFound("No such file.") + + q = session.query(File).filter(File.name==name)\ + .order_by(File.changed)\ + .limit(1) + if rev: + q = q.offset(rev) + + file_info = q.all() if not file_info: - raise NotFound("No such file: %s" % file_id) - + raise NotFound("No such file: %s" % name) + + file_info = file_info[0] + + path = os.path.join(request.registry.settings['file_path'], + str(file_info.id)) + try: + file = open(path) + except: + raise NotFound("No such file (though it should exist).") + + response = request.response + response.app_iter = file + return response + +# Limit files to 64 MBytes +FILE_SIZE_LIMIT = 1 << 26 +def files(request): + session = DBSession() + context = get_context(request) + context['page_name'] = '*Files' + context['page_subtitle'] = 'List of uploaded files' + + if 'data' in request.POST: + field = request.POST.getone('data') + name = field.filename + if '\\' in name: + name = name.split('\\')[-1] + if '/'in name: + name = name.split('/')[-1] + + file = File(name, context['user'].uid) + session.add(file) + session.flush() + path = os.path.join(request.registry.settings['file_path'], + str(file.id)) + + out_file = open(path,'w') + field.file.seek(0) + data = field.file.read(1<<16) + total_size = len(data) + while data and total_size < FILE_SIZE_LIMIT: + out_file.write(data) + data = field.file.read(1<<16) + total_size += len(data) + out_file.close() + + file.size = total_size + session.add(file) + session.flush() + + if total_size > FILE_SIZE_LIMIT: + # We limit the size of files, just in case. + os.remove(path) + context['message'] = 'File is too large.' + transaction.abort() + else: + context['message'] = 'File submitted successfully.' + #transaction.commit() + + + files = session.query(File) + files_by_name = {} + for file in files: + file_list = files_by_name.get(file.name, []) + file_list.append(file) + files_by_name[file.name] = file_list + + for file_list in files_by_name.values(): + file_list.sort(lambda a,b: cmp(a.changed, b.changed)) + + context['files'] = files_by_name + + return context diff --git a/development.ini b/development.ini index 99ffce8..4d51636 100644 --- a/development.ini +++ b/development.ini @@ -1,9 +1,9 @@ [app:basic_site] use = egg:basic_site reload_templates = true -debug_authorization = false -debug_notfound = false -debug_routematch = false +debug_authorization = true +debug_notfound = true +debug_routematch = true debug_templates = true default_locale_name = en sqlalchemy.url = sqlite:///%(here)s/basic_site.db @@ -12,6 +12,7 @@ site_name = "Basic Site GoshDarnit" sess_secret = 'this is a terrible secret' auth_secret = 'this is a worse secret' mako.directories = /tmp/mako +mako.strict_undefined=true [pipeline:main] pipeline = @@ -25,8 +26,8 @@ commit_veto = repoze.tm:default_commit_veto [server:main] use = egg:Paste#http -host = 0.0.0.0 -port = 6543 +host = 69.164.206.246 +port = 28288 # Begin logging configuration