from dataclasses import dataclass
import markdown
import mistune
import os
import web
import PIL.Image
import typing
PER_PAGE = 16
def slugify(string):
def process(char):
if char in "- \n\t\f":
return "-"
elif char.isalpha():
return char.lower()
else:
return ""
return "".join(process(c) for c in string)[:40]
class Image(typing.NamedTuple):
id: int
image: str
def thumb(self):
return self.image[:-4] + "_thumb" + self.image[-4:]
class PageContent:
def __init__(self, source):
self.source = source
def rendered(self):
return markdown.markdown(self.source)
def snipped(self):
if len(self.source) < 256:
return self.source
else:
return self.source[:256] + "..."
def to_slate(self):
return mistune.create_markdown(renderer=mistune.AstRenderer())(self.source)
@dataclass
class PageRef:
page: int
def __iter__(self):
return [self]
@dataclass
class Design:
title: str
images: typing.List[Image]
description: PageContent
category: str
id: int
def id_str(self):
return f"{self.id:05}"
def slug(self):
return slugify(self.title)
def thumbnail(self):
if self.images:
return self.images[0].thumb()
def tags(self, db):
return db.get_tags_for_design(self.id)
def category_list(self, db):
categories = db.all_categories()
return [
{"name": c.name, "selected": c.name == self.category}
for c in categories.elements
]
@classmethod
def list(cls, query_results) -> typing.List["Design"]:
return [
cls(
title=d.title,
images=[Image(image=d.image, id=0)],
description=d.description,
category=d.cat_name,
id=d.id,
)
for d in query_results
]
def to_json(self, db):
return {
"id": self.id,
"title": self.title,
"tags": self.tags(db),
"categories": self.category_list(db),
"description": self.description.source,
"slate": self.description.to_slate(),
"photos": self.images,
}
@dataclass
class Paginated:
next_page: typing.Optional[dict]
prev_page: typing.Optional[dict]
last_page: int
contents: typing.List[Design]
@classmethod
def paginate(
cls, offset: int, total: int, contents: typing.List[Design],
) -> "Paginated":
last_page = total // PER_PAGE
next_page = PageRef(page=offset + 1) if offset < last_page else None
prev_page = PageRef(page=offset - 1) if offset > 0 else None
return cls(
next_page=next_page,
prev_page=prev_page,
last_page=last_page,
contents=contents,
)
@dataclass
class Tag:
tag: str
count: int
def pretty(self):
return " ".join(w.capitalize() for w in self.tag.split())
@dataclass
class Link:
url: str
name: str
@classmethod
def from_category(cls, c):
return cls(name=c.nicename, url=f"/category/{c.name}")
@classmethod
def from_tag(cls, t):
nicename = " ".join(w.capitalize() for w in t.tag_name.split())
return cls(name=f"{nicename} ({t.n})", url=f"/tag/{t.tag_name}")
@dataclass
class LinkList:
elements: typing.List[Link]
THUMB_SIZE = (100, 100)
class DB:
def __init__(self, per_page=16):
self._db = web.database(dbn="sqlite", db="frony.db")
self.per_page = PER_PAGE
self.categories = {}
self.photo_num = self.get_max_photo_num() + 1
def get_max_photo_num(self):
n = 0
for file in os.listdir(os.path.join("static", "photos")):
try:
n = max(int(file[:5]), n)
except:
pass
return n
def get_category_id(self, nm):
id = list(self._db.where("categories", name=nm))
return id and id[0].id
def all_categories(self):
categories = self._db.select("categories")
return LinkList(elements=[Link.from_category(c) for c in categories])
def get_category_name(self, id):
if id in self.categories:
return self.categories[id]
else:
nm = list(self._db.where("categories", id=id))
if nm:
self.categories[id] = nm[0].name
return nm and nm[0].name or "unknown"
def get_design(self, id):
d = list(self._db.where("designs", id=id))
if not d:
return None
else:
d = d[0]
return Design(
title=d.title,
images=self.get_all_photos_for_design(d.id),
description=PageContent(d.description),
category=self.get_category_name(d.category),
id=d.id,
)
def put_design(self, id, title, description, category):
description = description.replace("\r\n", "\n")
self._db.update(
"designs",
where="id = $id",
vars=dict(id=id),
title=title,
description=description,
category=category,
)
def get_photo_by_id(self, id):
try:
photo = self._db.where("photos", id=id)[0]
return Image(image=photo.filename, id=photo.id)
except:
return None
def get_photo_by_filename(self, filename):
try:
photo = self._db.where("photos", filename=filename)[0]
return Image(image=photo.filename, id=photo.id)
except:
return None
def get_picture(self, id):
try:
photo = self._db.where("photos", design_id=id, limit=1)[0]
return Image(image=photo.filename, id=photo.id)
except:
return None
def get_all_photos(self, offset=0, pp=None):
if not pp:
pp = self.per_page
return [
Image(image=l.filename, id=l.id)
for l in self._db.select(
"photos", offset=offset * pp, limit=pp, order="id DESC"
)
]
def get_all_photos_for_design(self, id):
return list(
Image(image=d.filename, id=d.id)
for d in self._db.where("photos", design_id=id)
)
def get_all(self, offset=0):
ds = self._db.query(
"""select d.title, d.description, d.id, c.name as cat_name,
(select filename from photos where photos.design_id = d.id limit 1) as image
from designs d, categories c
where d.category = c.id
order by d.id desc
limit $per_page offset $offset""",
vars={"per_page": PER_PAGE, "offset": offset},
)
total = self._db.query("select count(*) as c from designs")
return Paginated.paginate(offset, total[0].c, Design.list(ds))
def get_designs_by_category(self, cat, offset=0):
ds = self._db.query(
"""select d.title, d.description, d.id, c.name as cat_name,
(select filename from photos where photos.design_id = d.id limit 1) as image
from designs d, categories c
where d.category = c.id
and c.name = $cat
order by d.id desc
limit $per_page offset $offset""",
vars={"cat": cat, "per_page": PER_PAGE, "offset": offset},
)
total = self._db.query(
"""select count(*) as c from designs d, categories c
where d.category = c.id and c.name = $cat""",
vars={"cat": cat},
)
return Paginated.paginate(offset, total[0].c, Design.list(ds))
def get_designs_by_category_and_tag(self, cat, tag, offset=0):
ds = self._db.query(
"""select d.title, d.description, d.id, c.name as cat_name,
(select filename from photos where photos.design_id = d.id limit 1) as image
from designs d, tags t, categories c
where t.tag_name = $tag
and d.category=c.id
and c.name = $cat
and d.id = t.design_id
order by d.id desc
limit $per_page offset $offset""",
vars=dict(
tag=tag, cat=cat, offset=offset * self.per_page, per_page=self.per_page
),
)
total = self._db.query(
"""select count(*) as c from designs, tags, categories
where designs.category = categories.id
and categories.name = $cat
and tags.tag_name = $tag
and designs.id = tags.design_id""",
vars=dict(tag=tag, cat=cat),
)
return Paginated.paginate(offset, total[0].c, Design.list(ds))
def new_design(self):
new_id = self._db.query("select max(id) as n from designs")[0].n + 1
self._db.insert(
"designs", id=new_id, title="New Design", description="", category=0
)
return new_id
def delete_design(self, id):
self._db.delete("designs", where="id = $id", vars=dict(id=id))
def add_photo(self, file, d_id):
self.photo_num = self.get_max_photo_num() + 1
new_id = self._db.query("select max(id) as n from photos")[0].n + 1
new_num = self.photo_num
self.photo_num += 1
extension = file.filename[-3:]
name = "{0:05}.{1}".format(new_num, extension)
thumb = "{0:05}_thumb.{1}".format(new_num, extension)
img = PIL.Image.open(file.stream)
img.thumbnail((400, 400), PIL.Image.ANTIALIAS)
img.save(os.path.join(os.getcwd(), "static", "photos", name))
img.thumbnail(THUMB_SIZE, PIL.Image.ANTIALIAS)
img.save(os.path.join("static", "photos", thumb))
self._db.insert("photos", id=new_id, filename=name, design_id=d_id)
return new_id
def delete_photo(self, photo_name):
self._db.delete(
"photos", where="filename = $filename", vars=dict(filename=photo_name)
)
def max_photo_page_ranges(self):
num_designs = self._db.query("select count(*) as n from photos")[0].n
return (0, (num_designs // self.per_page))
def max_page_ranges(self):
num_designs = self._db.query("select count(*) as n from designs")[0].n
return (0, (num_designs // self.per_page))
def max_page_range_for_category(self, cat):
cat_id = self.get_category_id(cat)
num_designs = self._db.query(
"select count(*) as n from designs where category = $cat",
vars=dict(cat=cat_id),
)[0].n
return (0, 1 + (num_designs // self.per_page))
def get_all_tags(self):
tags = self._db.query(
"""select t.tag_name, count(*) as n from designs d, tags t
where d.id = t.design_id group by t.tag_name"""
)
return LinkList(elements=sorted([Link.from_tag(t) for t in tags]))
def get_designs_by_tag(self, tag, offset=0):
ds = self._db.query(
"""select d.title, d.description, d.id, c.name as cat_name,
(select filename from photos where photos.design_id = d.id limit 1) as image
from designs d, tags t, categories c
where t.tag_name = $tag
and d.id = t.design_id
and d.category = c.id
order by d.id desc
limit $per_page offset $offset""",
vars=dict(tag=tag, offset=offset * self.per_page, per_page=self.per_page),
)
total = self._db.query(
"""select count(*) as c from designs d, tags t
where t.tag_name = $tag and d.id = t.design_id""",
vars={"tag": tag},
)
return Paginated.paginate(offset, total[0].c, Design.list(ds))
def get_tags_for_design(self, design_id):
return [
t.tag_name
for t in self._db.select(
"tags",
what="tag_name",
where="design_id = $d_id",
vars=dict(d_id=design_id),
)
]
def process_tag_list(self, design_id, tag_string):
new_tag_id = (
lambda: (self._db.query("select max(id) as n from tags")[0].n or 0) + 1
)
tag_list = tag_string.lower().split()
self._db.delete("tags", where="design_id = $d_id", vars=dict(d_id=design_id))
for t in tag_list:
self._db.insert("tags", id=new_tag_id(), tag_name=t, design_id=design_id)
def max_page_range_for_tag(self, tag):
num_designs = self._db.query(
"select count(*) as n from tags where tag_name = $t", vars=dict(t=tag)
)[0].n
return (0, (num_designs // self.per_page))
def num_for_tag(self, tag):
return self._db.query(
"select count(*) as n from tags where tag_name = $t", vars={"t": tag}
)[0].n
def get_clacker(self, new_visitor=None):
if new_visitor is not None:
exists = self._db.query(
"select count(*) as n from visits where visitor = $v",
vars={"v": new_visitor},
)[0].n
else:
exists = 1
total = self._db.query("select count(*) as n from visits")[0].n
if exists == 0 and new_visitor:
self._db.insert("visits", visitor=new_visitor)
return total + 1
else:
return total