#15 Add view module for mediating between model and template

Обединени
getty обедини 7 ревизии от getty/gr/add-views във getty/master преди 4 години
променени са 6 файла, в които са добавени 175 реда и са изтрити 70 реда
  1. 44 24
      lc/app.py
  2. 35 39
      lc/model.py
  3. 90 0
      lc/view.py
  4. 4 3
      lc/web.py
  5. 1 1
      stubs/pystache/__init__.py
  6. 1 3
      templates/link.mustache

+ 44 - 24
lc/app.py

@@ -7,6 +7,7 @@ import lc.config as c
 import lc.error as e
 import lc.model as m
 import lc.request as r
+import lc.view as v
 from lc.web import Endpoint, endpoint, render
 
 app = c.app
@@ -17,13 +18,17 @@ class Index(Endpoint):
     def html(self):
         return render(
             "main",
-            title="main",
-            content=render(
-                "message",
-                title="Lament Configuration",
-                message="Bookmark organizing for real pinheads.",
+            v.Page(
+                title="main",
+                content=render(
+                    "message",
+                    v.Message(
+                        title="Lament Configuration",
+                        message="Bookmark organizing for real pinheads.",
+                    ),
+                ),
+                user=self.user,
             ),
-            user=self.user,
         )
 
 
@@ -38,7 +43,9 @@ class Auth(Endpoint):
 @endpoint("/login")
 class Login(Endpoint):
     def html(self):
-        return render("main", title="login", content=render("login"), user=self.user)
+        return render(
+            "main", v.Page(title="login", content=render("login"), user=self.user,)
+        )
 
 
 @endpoint("/logout")
@@ -66,9 +73,7 @@ class CreateUser(Endpoint):
 
         return render(
             "main",
-            title="add user",
-            user=self.user,
-            content=render("add_user", token=token),
+            v.Page(title="add user", user=self.user, content=render("add_user"),),
         )
 
     def api_post(self):
@@ -87,9 +92,11 @@ class GetUser(Endpoint):
         links, pages = u.get_links(as_user=self.user, page=pg)
         return render(
             "main",
-            title=f"user {u.name}",
-            content=render("linklist", links=links, pages=pages),
-            user=self.user,
+            v.Page(
+                title=f"user {u.name}",
+                content=render("linklist", v.LinkList(links=links, pages=pages)),
+                user=self.user,
+            ),
         )
 
     def api_get(self, slug: str):
@@ -102,9 +109,11 @@ class UserConfig(Endpoint):
         u = self.require_authentication(user)
         return render(
             "main",
-            title="configuration",
-            content=render("config", **u.get_config()),
-            user=self.user,
+            v.Page(
+                title="configuration",
+                content=render("config", u.get_config()),
+                user=self.user,
+            ),
         )
 
 
@@ -119,7 +128,9 @@ class CreateInvite(Endpoint):
 @endpoint("/u/<string:user>/l")
 class CreateLink(Endpoint):
     def html(self, user: str):
-        return render("main", title="login", content=render("add_link"), user=self.user)
+        return render(
+            "main", v.Page(title="login", content=render("add_link"), user=self.user,)
+        )
 
     def api_post(self, user: str):
         u = self.require_authentication(user)
@@ -144,9 +155,11 @@ class GetLink(Endpoint):
         l = m.User.by_slug(user).get_link(int(link))
         return render(
             "main",
-            title=f"link {l.name}",
-            content=render("linklist", links=[l]),
-            user=self.user,
+            v.Page(
+                title=f"link {l.name}",
+                content=render("linklist", v.LinkList([l])),
+                user=self.user,
+            ),
         )
 
 
@@ -156,7 +169,12 @@ class EditLink(Endpoint):
         u = self.require_authentication(slug)
         l = u.get_link(int(link))
         return render(
-            "main", title="login", content=render("edit_link", link=l), user=self.user
+            "main",
+            v.Page(
+                title="login",
+                content=render("edit_link", v.SingleLink(l)),
+                user=self.user,
+            ),
         )
 
 
@@ -169,7 +187,9 @@ class GetTaggedLinks(Endpoint):
         links, pages = t.get_links(as_user=self.user, page=pg)
         return render(
             "main",
-            title=f"tag {tag}",
-            content=render("linklist", links=links, pages=pages),
-            user=self.user,
+            v.Page(
+                title=f"tag {tag}",
+                content=render("linklist", v.LinkList(links=links, pages=pages,)),
+                user=self.user,
+            ),
         )

+ 35 - 39
lc/model.py

@@ -8,6 +8,7 @@ from typing import List, Optional, Tuple
 import lc.config as c
 import lc.error as e
 import lc.request as r
+import lc.view as v
 
 
 class Model(peewee.Model):
@@ -18,26 +19,6 @@ class Model(peewee.Model):
         return playhouse.shortcuts.model_to_dict(self)
 
 
-@dataclass
-class Pagination:
-    current: int
-    last: int
-
-    def previous(self) -> Optional[dict]:
-        if self.current > 1:
-            return {"page": self.current - 1}
-        return None
-
-    def next(self) -> Optional[dict]:
-        if self.current < self.last:
-            return {"page": self.current + 1}
-        return None
-
-    @classmethod
-    def from_total(cls, current, total) -> "Pagination":
-        return cls(current=current, last=((total - 1) // c.per_page) + 1,)
-
-
 class User(Model):
     """
     A user! you know tf this is about
@@ -90,15 +71,18 @@ class User(Model):
     def base_url(self) -> str:
         return f"/u/{self.name}"
 
-    def get_links(self, as_user: r.User, page: int) -> Tuple[List["Link"], Pagination]:
+    def get_links(
+        self, as_user: r.User, page: int
+    ) -> Tuple[List[v.Link], v.Pagination]:
         links = (
             Link.select()
             .where((Link.user == self) & ((self == as_user) | (Link.private == False)))
             .order_by(-Link.created)
             .paginate(page, c.per_page)
         )
-        pagination = Pagination.from_total(page, Link.select().count())
-        return links, pagination
+        link_views = [l.to_view(as_user) for l in links]
+        pagination = v.Pagination.from_total(page, Link.select().count())
+        return link_views, pagination
 
     def get_link(self, link_id: int) -> "Link":
         return Link.get((Link.user == self) & (Link.id == link_id))
@@ -109,22 +93,19 @@ class User(Model):
     def to_dict(self) -> dict:
         return {"id": self.id, "name": self.name}
 
-    def get_config(self) -> dict:
+    def get_config(self) -> v.Config:
         admin_pane = None
         if self.is_admin:
             user_invites = [
-                {
-                    "claimed": ui.claimed_by is not None,
-                    "claimant": ui.claimed_by and ui.claimed_by.name,
-                    "token": ui.token,
-                }
+                v.UserInvite(
+                    claimed=ui.claimed_by is not None,
+                    claimant=ui.claimed_by and ui.claimed_by.name,
+                    token=ui.token,
+                )
                 for ui in UserInvite.select().where(UserInvite.created_by == self)
             ]
-            admin_pane = {"invites": user_invites}
-        return {
-            "username": self.name,
-            "admin_pane": admin_pane,
-        }
+            admin_pane = v.AdminPane(invites=user_invites)
+        return v.Config(username=self.name, admin_pane=admin_pane,)
 
 
 class Link(Model):
@@ -149,7 +130,6 @@ class Link(Model):
     def by_id(id: int) -> Optional["Link"]:
         return Link.get_or_none(id=id)
 
-
     @staticmethod
     def from_request(user: User, link: r.Link) -> "Link":
         l = Link.create(
@@ -171,7 +151,7 @@ class Link(Model):
 
         req_tags = set(link.tags)
 
-        for hastag in self.tags:
+        for hastag in self.tags:  # type: ignore
             name = hastag.tag.name
             if name not in req_tags:
                 hastag.delete_instance()
@@ -188,6 +168,19 @@ class Link(Model):
         self.private = link.private
         self.save()
 
+    def to_view(self, as_user: User) -> v.Link:
+        return v.Link(
+            id=self.id,
+            url=self.url,
+            name=self.name,
+            description=self.description,
+            private=self.private,
+            tags=[t.tag.to_view() for t in self.tags],  # type: ignore
+            created=self.created,
+            is_mine=self.user.id == as_user.id,
+            link_url=self.link_url(),
+        )
+
 
 class Tag(Model):
     """
@@ -201,9 +194,9 @@ class Tag(Model):
     def url(self) -> str:
         return f"/u/{self.user.name}/t/{self.name}"
 
-    def get_links(self, as_user: r.User, page: int) -> Tuple[List[Link], Pagination]:
+    def get_links(self, as_user: r.User, page: int) -> Tuple[List[Link], v.Pagination]:
         links = [
-            ht.link
+            ht.link.to_view(as_user)
             for ht in HasTag.select()
             .join(Link)
             .where(
@@ -213,7 +206,7 @@ class Tag(Model):
             .order_by(-Link.created)
             .paginate(page, c.per_page)
         ]
-        pagination = Pagination.from_total(
+        pagination = v.Pagination.from_total(
             page, HasTag.select().where((HasTag.tag == self)).count(),
         )
         return links, pagination
@@ -230,6 +223,9 @@ class Tag(Model):
 
         return Tag.create(name=tag_name, parent=parent, user=user)
 
+    def to_view(self) -> v.Tag:
+        return v.Tag(url=self.url(), name=self.name)
+
 
 class HasTag(Model):
     """

+ 90 - 0
lc/view.py

@@ -0,0 +1,90 @@
+from dataclasses import dataclass
+from datetime import datetime
+from typing import Any, Optional, List
+
+import lc.config as c
+
+
+class View:
+    pass
+
+
+@dataclass
+class Pagination(View):
+    current: int
+    last: int
+
+    def previous(self) -> Optional[dict]:
+        if self.current > 1:
+            return {"page": self.current - 1}
+        return None
+
+    def next(self) -> Optional[dict]:
+        if self.current < self.last:
+            return {"page": self.current + 1}
+        return None
+
+    @classmethod
+    def from_total(cls, current, total) -> "Pagination":
+        return cls(current=current, last=((total - 1) // c.per_page) + 1,)
+
+
+@dataclass
+class UserInvite(View):
+    claimed: bool
+    claimant: str
+    token: str
+
+
+@dataclass
+class AdminPane(View):
+    invites: List[UserInvite]
+
+
+@dataclass
+class Config(View):
+    username: str
+    admin_pane: Optional[AdminPane]
+
+
+@dataclass
+class Tag(View):
+    url: str
+    name: str
+
+
+@dataclass
+class Link(View):
+    id: int
+    url: str
+    name: str
+    description: str
+    private: bool
+    tags: List[Tag]
+    created: datetime
+    is_mine: bool
+    link_url: str
+
+
+@dataclass
+class LinkList(View):
+    links: List[Any]
+    pages: Optional[Pagination] = None
+
+
+@dataclass
+class SingleLink(View):
+    link: Any
+
+
+@dataclass
+class Message(View):
+    title: str
+    message: str
+
+
+@dataclass
+class Page(View):
+    title: str
+    content: str
+    user: Optional[str]

+ 4 - 3
lc/web.py

@@ -1,12 +1,13 @@
 from dataclasses import dataclass
 import flask
 import pystache
-from typing import TypeVar, Type
+from typing import Optional, TypeVar, Type
 
 import lc.config as c
 import lc.error as e
 import lc.model as m
 import lc.request as r
+import lc.view as v
 
 
 T = TypeVar("T", bound=r.Request)
@@ -166,8 +167,8 @@ def endpoint(route: str):
 LOADER = pystache.loader.Loader(extension="mustache", search_dirs=["templates"])
 
 
-def render(name: str, **kwargs) -> str:
+def render(name: str, data: Optional[v.View] = None) -> str:
     """Load and use a Mustache template from the project root"""
     template = LOADER.load_name(name)
     renderer = pystache.Renderer(missing_tags="strict", search_dirs=["templates"])
-    return renderer.render(template, kwargs)
+    return renderer.render(template, data or {})

+ 1 - 1
stubs/pystache/__init__.py

@@ -7,5 +7,5 @@ class Renderer:
     def __init__(self, missing_tags: str, search_dirs: List[str]):
         pass
 
-    def render(self, template: Any, kwargs: dict):
+    def render(self, template: Any, kwargs: Any):
         pass

+ 1 - 3
templates/link.mustache

@@ -2,9 +2,7 @@
   <div class="text"><a href="{{url}}">{{name}}</a></div>
   <div class="url"><a href="{{url}}">{{url}}</a></div>
   <div class="taglist">{{#tags}}
-    {{#tag}}
-      <span class="tag"><a href="{{url}}">{{name}}</a></span>
-    {{/tag}}
+    <span class="tag"><a href="{{url}}">{{name}}</a></span>
   {{/tags}}</div>
   <div class="datetime">
     <a href="{{link_url}}">{{created}}</a>