Add PagePolicy and PagePolicyKey tables, some indexes and flags on Page table.

Schema changes; please run migration tool on existing Page table to continue using:

    from playhouse.migrate import SqliteMigrator, migrate
    migrator = SqliteMigrator(database)
    migrate(
        migrator.drop_column('page', 'is_redirect'),
        migrator.add_column('page', 'flags', BitField())
    )
This commit is contained in:
Yusur 2021-04-04 20:05:19 +02:00
parent 6b1c57726d
commit 53559dc4f2

104
app.py
View file

@ -47,6 +47,18 @@ UPLOAD_DIR = APP_BASE_DIR + '/media'
DATABASE_DIR = APP_BASE_DIR + "/database" DATABASE_DIR = APP_BASE_DIR + "/database"
#### misc. helpers ####
def _makelist(l):
if isinstance(l, (str, bytes, bytearray)):
return [l]
elif hasattr(l, '__iter__'):
return list(l)
elif l:
return [l]
else:
return []
#### DATABASE SCHEMA #### #### DATABASE SCHEMA ####
database = SqliteDatabase(DATABASE_DIR + '/data.sqlite') database = SqliteDatabase(DATABASE_DIR + '/data.sqlite')
@ -55,11 +67,18 @@ class BaseModel(Model):
class Meta: class Meta:
database = database database = database
# Used for PagePolicy
def _passphrase_hash(pp):
pp_bin = pp.encode('utf-8')
h = str(len(pp_bin)) + ':' + hashlib.sha256(pp_bin).hexdigest()
return h
class Page(BaseModel): class Page(BaseModel):
url = CharField(64, unique=True, null=True) url = CharField(64, unique=True, null=True)
title = CharField(256) title = CharField(256, index=True)
is_redirect = BooleanField() touched = DateTimeField(index=True)
touched = DateTimeField() flags = BitField()
is_redirect = flags.flag(1)
@property @property
def latest(self): def latest(self):
if self.revisions: if self.revisions:
@ -94,6 +113,21 @@ class Page(BaseModel):
@property @property
def prop(self): def prop(self):
return PagePropertyDict(self) return PagePropertyDict(self)
def unlock(self, perm, pp, sec):
## XX complete later!
policies = self.policies.where(Policy.type << _makelist(perm))
if not policies.exists():
return True
for policy in policies:
if policy.verify(pp, sec):
return True
return False
def is_locked(self, perm):
policies = self.policies.where(Policy.type << _makelist(perm))
return policies.exists()
def is_classified(self):
return self.is_locked(POLICY_CLASSIFY)
class PageText(BaseModel): class PageText(BaseModel):
content = BlobField() content = BlobField()
@ -125,11 +159,11 @@ class PageText(BaseModel):
) )
class PageRevision(BaseModel): class PageRevision(BaseModel):
page = FK(Page, backref='revisions') page = FK(Page, backref='revisions', index=True)
user_id = IntegerField(default=0) user_id = IntegerField(default=0)
comment = CharField(256, default='') comment = CharField(1024, default='')
textref = FK(PageText) textref = FK(PageText)
pub_date = DateTimeField() pub_date = DateTimeField(index=True)
length = IntegerField() length = IntegerField()
@property @property
def text(self): def text(self):
@ -138,8 +172,8 @@ class PageRevision(BaseModel):
return md(self.text) return md(self.text)
class PageTag(BaseModel): class PageTag(BaseModel):
page = ForeignKeyField(Page, backref='tags') page = FK(Page, backref='tags', index=True)
name = CharField(64) name = CharField(64, index=True)
class Meta: class Meta:
indexes = ( indexes = (
(('page', 'name'), True), (('page', 'name'), True),
@ -148,7 +182,7 @@ class PageTag(BaseModel):
return PageTag.select().where(PageTag.name == self.name).count() return PageTag.select().where(PageTag.name == self.name).count()
class PageProperty(BaseModel): class PageProperty(BaseModel):
page = ForeignKeyField(Page, backref='page_meta') page = ForeignKeyField(Page, backref='page_meta', index=True)
key = CharField(64) key = CharField(64)
value = CharField(8000) value = CharField(8000)
class Meta: class Meta:
@ -199,14 +233,49 @@ class PagePropertyDict(object):
return PageProperty.select().where((PageProperty.page == self._page) & return PageProperty.select().where((PageProperty.page == self._page) &
(PageProperty.key == key)).exists() (PageProperty.key == key)).exists()
# Store keys for PagePolicy.
# Experimental.
class PagePolicyKey(BaseModel):
passphrase = CharField()
sec_code = IntegerField()
class Meta:
indexes = (
(('passphrase','sec_code'), True),
)
@classmethod
def create_from_plain(cls, pp, sec):
PagePolicyKey.create(passphrase=_passphrase_hash(pp), sec_code=sec)
def verify(self, pp, sec):
h = _passphrase_hash(pp)
return self.passphrase == h and self.sec_code == sec
POLICY_ADMIN = 1
POLICY_READ = 2
POLICY_EDIT = 3
POLICY_META = 4
POLICY_CLASSIFY = 5
# Manage policies for pages (e.g., reading or editing).
# Experimental.
class PagePolicy(BaseModel):
page = FK(Page, backref='policies', index=True, null=True)
type = IntegerField()
key = FK(PagePolicyKey, backref='applied_to')
sitewide = IntegerField(default=0)
class Meta:
indexes = (
(('page', 'key'), True),
)
class Upload(BaseModel): class Upload(BaseModel):
name = CharField(256) name = CharField(256)
url_name = CharField(256, null=True) url_name = CharField(256, null=True)
filetype = SmallIntegerField() filetype = SmallIntegerField()
filesize = IntegerField() filesize = IntegerField()
upload_date = DateTimeField() upload_date = DateTimeField(index=True)
md5 = CharField(32) md5 = CharField(32, index=True)
@property @property
def filepath(self): def filepath(self):
return '{0}/{1}/{2}{3}.{4}'.format(self.md5[:1], self.md5[:2], self.id, return '{0}/{1}/{2}{3}.{4}'.format(self.md5[:1], self.md5[:2], self.id,
@ -257,7 +326,7 @@ class Upload(BaseModel):
return obj return obj
def init_db(): def init_db():
database.create_tables([Page, PageText, PageRevision, PageTag, PageProperty, Upload]) database.create_tables([Page, PageText, PageRevision, PageTag, PageProperty, PagePolicyKey, PagePolicy, Upload])
#### WIKI SYNTAX #### #### WIKI SYNTAX ####
@ -390,7 +459,7 @@ forbidden_urls = [
'create', 'edit', 'p', 'ajax', 'history', 'manage', 'static', 'media', 'create', 'edit', 'p', 'ajax', 'history', 'manage', 'static', 'media',
'accounts', 'tags', 'init-config', 'upload', 'upload-info', 'about', 'accounts', 'tags', 'init-config', 'upload', 'upload-info', 'about',
'stats', 'terms', 'privacy', 'easter', 'search', 'help', 'circles', 'stats', 'terms', 'privacy', 'easter', 'search', 'help', 'circles',
'protect', 'protect',
] ]
app = Flask(__name__) app = Flask(__name__)
@ -438,13 +507,18 @@ def error_404(body):
return render_template('notfound.html'), 404 return render_template('notfound.html'), 404
# Helpers for page editing. # Middle point during page editing.
def savepoint(form, is_preview=False): def savepoint(form, is_preview=False):
if is_preview: if is_preview:
preview = md(form['text']) preview = md(form['text'])
else: else:
preview = None preview = None
return render_template('edit.html', pl_url=form['url'], pl_title=form['title'], pl_text=form['text'], pl_tags=form['tags'], preview=preview) pl_js_info = dict()
pl_js_info['editing'] = dict(
original_text = None, # TODO
preview_text = form['text'],
)
return render_template('edit.html', pl_url=form['url'], pl_title=form['title'], pl_text=form['text'], pl_tags=form['tags'], preview=preview, pl_js_info=pl_js_info)
@app.route('/create/', methods=['GET', 'POST']) @app.route('/create/', methods=['GET', 'POST'])
def create(): def create():