diff --git a/.idea/misc.xml b/.idea/misc.xml
index d09bbac..ad2d41e 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -4,6 +4,9 @@
/usr/local/bin/ghc
/usr/local/bin/stack
+
+
+
diff --git a/bookmarks/models.py b/bookmarks/models.py
index 0d5c3c7..964f307 100644
--- a/bookmarks/models.py
+++ b/bookmarks/models.py
@@ -42,8 +42,9 @@ class Bookmark(models.Model):
tags = models.ManyToManyField(Tag)
# Attributes might be calculated in query
- tag_count = 0
- tag_string = ''
+ tag_count = 0 # Projection for number of associated tags
+ tag_string = '' # Projection for list of tag names, comma-separated
+ tag_projection = False # Tracks if the above projections were loaded
@property
def resolved_title(self):
@@ -55,7 +56,8 @@ class Bookmark(models.Model):
@property
def tag_names(self):
- if self.tag_string:
+ # If tag projections were loaded then avoid querying all tags (=executing further selects)
+ if self.tag_string or self.tag_projection:
return parse_tag_string(self.tag_string)
else:
return [tag.name for tag in self.tags.all()]
diff --git a/bookmarks/queries.py b/bookmarks/queries.py
index 7450b30..de27a25 100644
--- a/bookmarks/queries.py
+++ b/bookmarks/queries.py
@@ -1,5 +1,5 @@
from django.contrib.auth.models import User
-from django.db.models import Q, Count, Aggregate, CharField
+from django.db.models import Q, Count, Aggregate, CharField, Value, BooleanField
from bookmarks.models import Bookmark, Tag
@@ -20,7 +20,8 @@ def query_bookmarks(user: User, query_string: str):
# Add aggregated tag info to bookmark instances
query_set = Bookmark.objects \
.annotate(tag_count=Count('tags'),
- tag_string=Concat('tags__name'))
+ tag_string=Concat('tags__name'),
+ tag_projection=Value(True, BooleanField()))
# Filter for user
query_set = query_set.filter(owner=user)
@@ -49,7 +50,7 @@ def query_bookmarks(user: User, query_string: str):
def query_tags(user: User, query_string: str):
- query_set = Tag.objects;
+ query_set = Tag.objects
# Filter for user
query_set = query_set.filter(owner=user)
diff --git a/bookmarks/services/bookmarks.py b/bookmarks/services/bookmarks.py
index 3e6c6d6..f440b90 100644
--- a/bookmarks/services/bookmarks.py
+++ b/bookmarks/services/bookmarks.py
@@ -1,10 +1,9 @@
-import requests
-from bs4 import BeautifulSoup
from django.contrib.auth.models import User
from django.utils import timezone
from bookmarks.models import Bookmark, BookmarkForm, parse_tag_string
from services.tags import get_or_create_tags
+from services.website_loader import load_website_metadata
def create_bookmark(form: BookmarkForm, current_user: User):
@@ -34,28 +33,12 @@ def update_bookmark(form: BookmarkForm, current_user: User):
def _update_website_metadata(bookmark: Bookmark):
- # noinspection PyBroadException
- try:
- page_text = load_page(bookmark.url)
- soup = BeautifulSoup(page_text, 'html.parser')
-
- title = soup.title.string if soup.title is not None else None
- description_tag = soup.find('meta', attrs={'name': 'description'})
- description = description_tag['content'] if description_tag is not None else None
-
- bookmark.website_title = title
- bookmark.website_description = description
- except Exception:
- bookmark.website_title = None
- bookmark.website_description = None
+ metadata = load_website_metadata(bookmark.url)
+ bookmark.website_title = metadata.title
+ bookmark.website_description = metadata.description
def _update_bookmark_tags(bookmark: Bookmark, tag_string: str, user: User):
tag_names = parse_tag_string(tag_string, ' ')
tags = get_or_create_tags(tag_names, user)
bookmark.tags.set(tags)
-
-
-def load_page(url: str):
- r = requests.get(url)
- return r.text
diff --git a/bookmarks/services/website_loader.py b/bookmarks/services/website_loader.py
new file mode 100644
index 0000000..5d00f6d
--- /dev/null
+++ b/bookmarks/services/website_loader.py
@@ -0,0 +1,37 @@
+from dataclasses import dataclass
+
+import requests
+from bs4 import BeautifulSoup
+
+
+@dataclass
+class WebsiteMetadata:
+ url: str
+ title: str
+ description: str
+
+ def to_dict(self):
+ return {
+ 'url': self.url,
+ 'title': self.title,
+ 'description': self.description,
+ }
+
+
+def load_website_metadata(url: str):
+ title = None
+ description = None
+ try:
+ page_text = load_page(url)
+ soup = BeautifulSoup(page_text, 'html.parser')
+
+ title = soup.title.string if soup.title is not None else None
+ description_tag = soup.find('meta', attrs={'name': 'description'})
+ description = description_tag['content'] if description_tag is not None else None
+ finally:
+ return WebsiteMetadata(url=url, title=title, description=description)
+
+
+def load_page(url: str):
+ r = requests.get(url)
+ return r.text
diff --git a/bookmarks/styles/bookmarks.scss b/bookmarks/styles/bookmarks.scss
index 1e02f17..06f789e 100644
--- a/bookmarks/styles/bookmarks.scss
+++ b/bookmarks/styles/bookmarks.scss
@@ -49,3 +49,10 @@ ul.bookmark-list {
color: $alternative-color-dark;
}
}
+
+.bookmarks-form {
+
+ .form-icon.loading {
+ visibility: hidden;
+ }
+}
diff --git a/bookmarks/templates/bookmarks/form.html b/bookmarks/templates/bookmarks/form.html
index 1ff922e..bede7c9 100644
--- a/bookmarks/templates/bookmarks/form.html
+++ b/bookmarks/templates/bookmarks/form.html
@@ -1,40 +1,81 @@
{% load widget_tweaks %}
-{% csrf_token %}
-