#14 Link editing form and endpoint

Merged
getty merged 8 commits from getty/moltarx/edit-links into getty/master 4 years ago
7 changed files with 120 additions and 2 deletions
  1. 17 0
      lc/app.py
  2. 1 0
      lc/error.py
  3. 34 0
      lc/model.py
  4. 5 1
      lc/web.py
  5. 30 0
      templates/edit_link.mustache
  6. 4 1
      templates/link.mustache
  7. 29 0
      tests/model.py

+ 17 - 0
lc/app.py

@@ -133,6 +133,13 @@ class GetLink(Endpoint):
     def api_get(self, user: str, link: str):
         pass
 
+    def api_post(self, user: str, link: str):
+        u = self.require_authentication(user)
+        l = u.get_link(int(link))
+        req = self.request_data(r.Link)
+        l.update_from_request(u, req)
+        raise e.LCRedirect(l.link_url())
+
     def html(self, user: str, link: str):
         l = m.User.by_slug(user).get_link(int(link))
         return render(
@@ -143,6 +150,16 @@ class GetLink(Endpoint):
         )
 
 
+@endpoint("/u/<string:slug>/l/<string:link>/edit")
+class EditLink(Endpoint):
+    def html(self, slug: str, link: str):
+        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
+        )
+
+
 @endpoint("/u/<string:user>/t/<path:tag>")
 class GetTaggedLinks(Endpoint):
     def html(self, user: str, tag: str):

+ 1 - 0
lc/error.py

@@ -27,6 +27,7 @@ class BadPayload(LCException):
     def http_code(self) -> int:
         return 400
 
+
 @dataclass
 class UserExists(LCException):
     name: str

+ 34 - 0
lc/model.py

@@ -145,6 +145,11 @@ class Link(Model):
     def link_url(self) -> str:
         return f"/u/{self.user.name}/l/{self.id}"
 
+    @staticmethod
+    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(
@@ -162,6 +167,27 @@ class Link(Model):
             )
         return l
 
+    def update_from_request(self, user: User, link: r.Link):
+
+        req_tags = set(link.tags)
+
+        for hastag in self.tags:
+            name = hastag.tag.name
+            if name not in req_tags:
+                hastag.delete_instance()
+            else:
+                req_tags.remove(name)
+
+        for tag_name in req_tags:
+            t = Tag.get_or_create_tag(user, tag_name)
+            HasTag.get_or_create(link=self, tag=t)
+
+        self.url = link.url
+        self.name = link.name
+        self.description = link.description
+        self.private = link.private
+        self.save()
+
 
 class Tag(Model):
     """
@@ -213,6 +239,14 @@ class HasTag(Model):
     link = peewee.ForeignKeyField(Link, backref="tags")
     tag = peewee.ForeignKeyField(Tag, backref="models")
 
+    @staticmethod
+    def get_or_create(link: Link, tag: Tag):
+        res = HasTag.get_or_none(link=link, tag=tag)
+        if res is None:
+            res = HasTag.create(link=link, tag=tag)
+
+        return res
+
 
 class UserInvite(Model):
     token = peewee.TextField(unique=True)

+ 5 - 1
lc/web.py

@@ -11,6 +11,7 @@ import lc.request as r
 
 T = TypeVar("T", bound=r.Request)
 
+
 @dataclass
 class ApiOK:
     response: dict
@@ -105,7 +106,10 @@ class Endpoint:
                 return ({"status": exn.http_code(), "error": str(exn)}, exn.http_code())
             else:
                 page = render(
-                    "main", title="error", content=f"shit's fucked yo: {exn}", user=None,
+                    "main",
+                    title="error",
+                    content=f"shit's fucked yo: {exn}",
+                    user=None,
                 )
                 return (page, exn.http_code())
         # also maybe we tried to redirect, so just do that

+ 30 - 0
templates/edit_link.mustache

@@ -0,0 +1,30 @@
+{{#link}}
+<div class="loginform">
+  <form name="edit_link" method="POST" action="{{link_url}}">
+    <div class="url">
+      <label for="url">URL</label>
+      <input name="url" type="text" value="{{url}}" />
+    </div>
+    <div class="name">
+      <label for="name">Link Name</label>
+      <input name="name" type="text" value="{{name}}" />
+    </div>
+    <div class="description">
+      <label for="description">Description</label>
+      <input name="description" type="text" value="{{description}}" />
+    </div>
+    <div class="private">
+      <label for="private">Private?</label>
+      <input name="private" type="checkbox" {{#private}}checked {{/private}}/>
+    </div>
+    <div class="tags">
+      <label for="tags">Tags</label>
+      <input name="tags" type="text" value="{{#tags}}{{#tag}}{{name}} {{/tag}}{{/tags}}" />
+    </div>
+
+    <div class="submit">
+      <input type="submit" value="Save" />
+    </div>
+  </form>
+</div>
+{{/link}}

+ 4 - 1
templates/link.mustache

@@ -6,5 +6,8 @@
       <span class="tag"><a href="{{url}}">{{name}}</a></span>
     {{/tag}}
   {{/tags}}</div>
-  <div class="datetime"><a href="{{link_url}}">{{created}}</a></div>
+  <div class="datetime">
+    <a href="{{link_url}}">{{created}}</a>
+    <a href="{{link_url}}/edit">edit</a>
+  </div>
 </div>

+ 29 - 0
tests/model.py

@@ -133,3 +133,32 @@ class Testdb:
 
         with pytest.raises(e.NoSuchInvite):
             m.User.from_invite(r.User(name="u4", password="u4"), "a-non-existent-token")
+
+    def check_tags(self, l, tags):
+        present = set(map(lambda hastag: hastag.tag.name, l.tags))
+        assert present == set(tags)
+
+    def test_edit_link(self):
+        u = self.mk_user()
+
+        req = r.Link("http://foo.com", "foo", "", False, ["foo", "bar"])
+        l = m.Link.from_request(u, req)
+        assert l.name == req.name
+        assert l.tags == ["foo", "bar"]
+
+        # check the in-place update
+        req.name = "bar"
+        req.tags = ["bar", "baz"]
+        req.private = True
+        l.update_from_request(u, req)
+        assert l.name == req.name
+        assert l.private
+        assert l.created != req.created
+        self.check_tags(l, req.tags)
+
+        # check that the link was persisted
+        l = m.Link.by_id(l.id)
+        assert l.name == req.name
+        assert l.private
+        assert l.created != req.created
+        self.check_tags(l, req.tags)