diff --git a/.gitignore b/.gitignore index af24c17..729280b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ content/ .eggs *.egg-info dist/ +.idea diff --git a/appveyor.yml b/appveyor.yml index b911faa..c9afc64 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -build: off +build: false environment: matrix: diff --git a/setup.py b/setup.py index 0420cce..a2ef4b9 100644 --- a/setup.py +++ b/setup.py @@ -10,16 +10,15 @@ author='Alexander Jung-Loddenkemper', author_email='alexander@julo.ch', url='https://github.com/alexanderjulo/wiki', - packages=find_packages(), + packages=find_packages(exclude=['tests']), include_package_data=True, python_requires='>3.7', install_requires=[ 'Flask>=0.9', - 'Click>=6,<7', + 'Click>=6', 'Flask-Login>=0.4', 'Flask-WTF>=0.8', 'Markdown>=2.2.0', - 'Pygments>=1.5', 'WTForms>=1.0.2', 'Werkzeug>=0.8.3', 'python-markdown-math' diff --git a/tests/__init__.py b/tests/__init__.py index 777d335..33693e9 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -2,7 +2,7 @@ import os import shutil from tempfile import mkdtemp -from tempfile import mkstemp +# from tempfile import mkstemp from unittest import TestCase from wiki.core import Wiki @@ -18,7 +18,6 @@ class WikiBaseTestCase(TestCase): - #: The contents of the ``config.py`` file. config_content = CONFIGURATION @@ -69,7 +68,6 @@ def create_file(self, name, content=u'', folder=None): return path - def tearDown(self): """ Will remove the root directory and all contents if one diff --git a/tests/test_core.py b/tests/test_core.py index 93b81c5..a948c79 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -14,7 +14,6 @@ from . import WikiBaseTestCase - PAGE_CONTENT = u"""\ title: Test tags: one, two, 3, jö @@ -28,12 +27,10 @@ Hello, how are you guys? """ - CONTENT_HTML = u"""\

Hello, how are you guys?

Is it not magnificent?

""" - WIKILINK_PAGE_CONTENT = u"""\ title: link @@ -100,7 +97,7 @@ def test_handle_spaces(self): other spaces correctly substituted. """ assert (clean_url(' /hello you/wonderful/person ') - == '/hello_you/wonderful/person') + == '/hello_you/wonderful/person') def test_handle_uppercase(self): """ @@ -228,7 +225,6 @@ def setUp(self): self.page_path = self.create_file('test.md', self.page_content) - def test_page_loading_fails(self): """ Assert that content is loaded correctly from disk. diff --git a/wiki/__init__.py b/wiki/__init__.py index b2e2239..180f704 100644 --- a/wiki/__init__.py +++ b/wiki/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from wiki.core import Wiki __all__ = ['Wiki'] diff --git a/wiki/cli.py b/wiki/cli.py index a3eeb1c..b436fea 100644 --- a/wiki/cli.py +++ b/wiki/cli.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ CLI ~~~ @@ -7,6 +8,7 @@ import click from wiki.web import create_app + @click.group() @click.option('--directory', type=click.Path(exists=True), default=None) @click.pass_context @@ -15,6 +17,7 @@ def main(ctx, directory): Base setup for all the following commands. \b + :param ctx: context? :param str directory: the directory to run wiki in, optional. If no directory is provided the current directory will be used. @@ -34,6 +37,7 @@ def web(ctx, debug, host, port): Run the web app. \b + :param ctx: context? :param bool debug: whether or not to run the web app in debug mode. :param str host: Set the host to 0.0.0.0 to connect from outside. diff --git a/wiki/core.py b/wiki/core.py index 58113f3..0c42ead 100644 --- a/wiki/core.py +++ b/wiki/core.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ Wiki core ~~~~~~~~~ @@ -97,7 +98,7 @@ def __init__(self, text): 'fenced_code', 'meta', 'tables', - 'mdx_math' # mathjax support + 'mdx_math' # mathjax support ]) self.input = text self.markdown = None @@ -123,7 +124,6 @@ def process_markdown(self): """ self.html = self.md.convert(self.pre) - def split_raw(self): """ Split text into raw meta and content. @@ -141,12 +141,13 @@ def process_meta(self): # entries, so we have to loop over the meta values a second # time to put them into a dictionary in the correct order self.meta = OrderedDict() - for line in self.meta_raw.split('\n'): - key = line.split(':', 1)[0] - # markdown metadata always returns a list of lines, we will - # reverse that here - self.meta[key.lower()] = \ - '\n'.join(self.md.Meta[key.lower()]) + if self.md.Meta: # skip meta-less + for line in self.meta_raw.split('\n'): + key = line.split(':', 1)[0] + # markdown metadata always returns a list of lines, we will + # reverse that here + self.meta[key.lower()] = \ + '\n'.join(self.md.Meta[key.lower()]) def process_post(self): """ @@ -174,6 +175,9 @@ def process(self): class Page(object): def __init__(self, path, url, new=False): + self.content = None + self._html = None + self.body = None self.path = path self.url = url self._meta = OrderedDict() @@ -321,7 +325,7 @@ def index(self): root = os.path.abspath(self.root) for cur_dir, _, files in os.walk(root): # get the url of the current directory - cur_dir_url = cur_dir[len(root)+1:] + cur_dir_url = cur_dir[len(root) + 1:] for cur_file in files: path = os.path.join(cur_dir, cur_file) if cur_file.endswith('.md'): @@ -382,7 +386,7 @@ def index_by_tag(self, tag): tagged.append(page) return sorted(tagged, key=lambda x: x.title.lower()) - def search(self, term, ignore_case=True, attrs=['title', 'tags', 'body']): + def search(self, term, ignore_case=True, attrs=('title', 'tags', 'body')): pages = self.index() regex = re.compile(term, re.IGNORECASE if ignore_case else 0) matched = [] diff --git a/wiki/web/__init__.py b/wiki/web/__init__.py index c1f5e91..9f13e0a 100644 --- a/wiki/web/__init__.py +++ b/wiki/web/__init__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- import os from flask import current_app @@ -9,23 +10,28 @@ from wiki.core import Wiki from wiki.web.user import UserManager + class WikiError(Exception): pass + def get_wiki(): wiki = getattr(g, '_wiki', None) if wiki is None: wiki = g._wiki = Wiki(current_app.config['CONTENT_DIR']) return wiki + current_wiki = LocalProxy(get_wiki) + def get_users(): users = getattr(g, '_users', None) if users is None: users = g._users = UserManager(current_app.config['CONTENT_DIR']) return users + current_users = LocalProxy(get_users) @@ -52,6 +58,7 @@ def create_app(directory): loginmanager = LoginManager() loginmanager.login_view = 'wiki.user_login' + @loginmanager.user_loader def load_user(name): return current_users.get_user(name) diff --git a/wiki/web/forms.py b/wiki/web/forms.py index 9da436d..3debc83 100644 --- a/wiki/web/forms.py +++ b/wiki/web/forms.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ Forms ~~~~~ @@ -12,6 +13,7 @@ from wtforms import PasswordField from wtforms.validators import InputRequired from wtforms.validators import ValidationError +from wtforms.validators import EqualTo from wiki.core import clean_url from wiki.web import current_wiki @@ -58,3 +60,15 @@ def validate_password(form, field): return if not user.check_password(field.data): raise ValidationError('Username and password do not match.') + + +class RegistrationForm(FlaskForm): + name = StringField('Username', [InputRequired()]) + password = PasswordField('Password', [InputRequired()]) + password_confirm = PasswordField('Confirm Password', + validators=[InputRequired(), EqualTo('password')]) + + def validate_name(form, field): + user = current_users.get_user(form.name.data) + if user: + raise ValidationError('This username already exist.') diff --git a/wiki/web/routes.py b/wiki/web/routes.py index af4cc2b..3b68d80 100644 --- a/wiki/web/routes.py +++ b/wiki/web/routes.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ Routes ~~~~~~ @@ -8,6 +9,7 @@ from flask import render_template from flask import request from flask import url_for +from flask import current_app from flask_login import current_user from flask_login import login_required from flask_login import login_user @@ -18,9 +20,11 @@ from wiki.web.forms import LoginForm from wiki.web.forms import SearchForm from wiki.web.forms import URLForm +from wiki.web.forms import RegistrationForm from wiki.web import current_wiki from wiki.web import current_users from wiki.web.user import protect +from wiki.web.user import UserManager bp = Blueprint('wiki', __name__) @@ -38,6 +42,11 @@ def home(): @bp.route('/index/') @protect def index(): + import locale + current_app.logger.info("defaultlocale: {}".format(locale.getdefaultlocale())) + current_app.logger.info("locale: {}".format(locale.getlocale())) + current_app.logger.info("preferredencoding: {}".format(locale.getpreferredencoding())) + locale.setlocale(locale.LC_ALL, 'ru_RU') pages = current_wiki.index() return render_template('index.html', pages=pages) @@ -50,6 +59,7 @@ def display(url): @bp.route('/create/', methods=['GET', 'POST']) +@login_required @protect def create(): form = URLForm() @@ -60,6 +70,7 @@ def create(): @bp.route('/edit//', methods=['GET', 'POST']) +@login_required @protect def edit(url): page = current_wiki.get(url) @@ -84,6 +95,7 @@ def preview(): @bp.route('/move//', methods=['GET', 'POST']) +@login_required @protect def move(url): page = current_wiki.get_or_404(url) @@ -96,6 +108,7 @@ def move(url): @bp.route('/delete//') +@login_required @protect def delete(url): page = current_wiki.get_or_404(url) @@ -155,9 +168,17 @@ def user_index(): pass -@bp.route('/user/create/') +@bp.route('/user/create/', methods=['GET', 'POST']) def user_create(): - pass + form = RegistrationForm() + if form.validate_on_submit(): + user = form.name.data + password = form.password.data + UserManager(current_app.config['CONTENT_DIR']).add_user(name=user, password=password) + # user.set('authenticated', True) + flash('Registration successful.', 'success') + return redirect(request.args.get("next") or url_for('wiki.index')) + return render_template('register.html', form=form) @bp.route('/user//') @@ -166,6 +187,7 @@ def user_admin(user_id): @bp.route('/user/delete//') +@login_required def user_delete(user_id): pass diff --git a/wiki/web/templates/base.html b/wiki/web/templates/base.html index 6ab00eb..dd2bd43 100644 --- a/wiki/web/templates/base.html +++ b/wiki/web/templates/base.html @@ -1,7 +1,9 @@ {% from "helpers.html" import input %} - + + + {{ config.TITLE }} @@ -39,10 +41,13 @@ diff --git a/wiki/web/templates/register.html b/wiki/web/templates/register.html new file mode 100644 index 0000000..9ef9512 --- /dev/null +++ b/wiki/web/templates/register.html @@ -0,0 +1,16 @@ +{% extends "base.html" %} + +{% block title %}Register{% endblock title %} + +{% block content %} +
+ {{ form.hidden_tag() }} + {{ form.name.label }}
+ {{ form.name(placeholder='Username') }}
+ {{ form.password.label }}
+ {{ form.password(placeholder='Password') }}
+ {{ form.password_confirm.label }}
+ {{ form.password_confirm(placeholder='Confirm Password') }}
+ +
+{% endblock content %} diff --git a/wiki/web/templates/search.html b/wiki/web/templates/search.html index 5907fc3..019a55c 100644 --- a/wiki/web/templates/search.html +++ b/wiki/web/templates/search.html @@ -14,7 +14,7 @@
{{ form.hidden_tag() }} {{ form.term(placeholder='Search for.. (regex accepted)', autocomplete="off") }} - {{ form.ignore_case() }} Ignore Case + {{ form.ignore_case() }} {{ form.ignore_case.label }}
diff --git a/wiki/web/user.py b/wiki/web/user.py index 5d51786..0691694 100644 --- a/wiki/web/user.py +++ b/wiki/web/user.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """ User classes & helpers ~~~~~~~~~~~~~~~~~~~~~~ @@ -12,9 +13,9 @@ from flask_login import current_user - class UserManager(object): """A very simple user Manager, that saves it's data as json.""" + def __init__(self, path): self.file = os.path.join(path, 'users.json') @@ -30,7 +31,7 @@ def write(self, data): f.write(json.dumps(data, indent=2)) def add_user(self, name, password, - active=True, roles=[], authentication_method=None): + active=True, roles=(), authentication_method=None): users = self.read() if users.get(name): return False @@ -145,4 +146,5 @@ def wrapper(*args, **kwargs): if current_app.config.get('PRIVATE') and not current_user.is_authenticated: return current_app.login_manager.unauthorized() return f(*args, **kwargs) + return wrapper