diff --git a/bookmarks/models.py b/bookmarks/models.py index 44ee27e..0d5c3c7 100644 --- a/bookmarks/models.py +++ b/bookmarks/models.py @@ -1,3 +1,5 @@ +from typing import List + from django import forms from django.contrib.auth import get_user_model from django.db import models @@ -12,6 +14,20 @@ class Tag(models.Model): return self.name +def parse_tag_string(tag_string: str, delimiter: str = ','): + if not tag_string: + return [] + names = tag_string.strip().split(delimiter) + names = [name for name in names if name] + names.sort(key=str.lower) + + return names + + +def build_tag_string(tag_names: List[str], delimiter: str = ','): + return delimiter.join(tag_names) + + class Bookmark(models.Model): url = models.URLField() title = models.CharField(max_length=512) @@ -39,9 +55,10 @@ class Bookmark(models.Model): @property def tag_names(self): - tag_names = self.tag_string.strip().split(',') if self.tag_string else [] - tag_names.sort(key=str.lower) - return tag_names + if self.tag_string: + return parse_tag_string(self.tag_string) + else: + return [tag.name for tag in self.tags.all()] def __str__(self): return self.resolved_title + ' (' + self.url[:30] + '...)' @@ -53,6 +70,7 @@ auto_fill_placeholder = 'Leave empty to fill from website metadata' class BookmarkForm(forms.ModelForm): # Use URLField for URL url = forms.URLField() + tag_string = forms.CharField(required=False) # Do not require title and description in form as we fill these automatically if they are empty title = forms.CharField(max_length=512, required=False) @@ -61,4 +79,4 @@ class BookmarkForm(forms.ModelForm): class Meta: model = Bookmark - fields = ['url', 'title', 'description'] + fields = ['url', 'tag_string', 'title', 'description'] diff --git a/bookmarks/services/bookmarks.py b/bookmarks/services/bookmarks.py index 8817a7b..3e6c6d6 100644 --- a/bookmarks/services/bookmarks.py +++ b/bookmarks/services/bookmarks.py @@ -3,10 +3,12 @@ from bs4 import BeautifulSoup from django.contrib.auth.models import User from django.utils import timezone -from bookmarks.models import Bookmark +from bookmarks.models import Bookmark, BookmarkForm, parse_tag_string +from services.tags import get_or_create_tags -def create_bookmark(bookmark: Bookmark, current_user: User): +def create_bookmark(form: BookmarkForm, current_user: User): + bookmark = form.save(commit=False) # Update website info _update_website_metadata(bookmark) # Set currently logged in user as owner @@ -15,11 +17,17 @@ def create_bookmark(bookmark: Bookmark, current_user: User): bookmark.date_added = timezone.now() bookmark.date_modified = timezone.now() bookmark.save() + # Update tag list + _update_bookmark_tags(bookmark, form.data['tag_string'], current_user) + bookmark.save() -def update_bookmark(bookmark: Bookmark): +def update_bookmark(form: BookmarkForm, current_user: User): + bookmark = form.save(commit=False) # Update website info _update_website_metadata(bookmark) + # Update tag list + _update_bookmark_tags(bookmark, form.data['tag_string'], current_user) # Update dates bookmark.date_modified = timezone.now() bookmark.save() @@ -42,6 +50,12 @@ def _update_website_metadata(bookmark: Bookmark): bookmark.website_description = None +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/importer.py b/bookmarks/services/importer.py index 6fa5469..a6809b4 100644 --- a/bookmarks/services/importer.py +++ b/bookmarks/services/importer.py @@ -3,9 +3,9 @@ from datetime import datetime import bs4 from bs4 import BeautifulSoup from django.contrib.auth.models import User -from django.utils import timezone -from bookmarks.models import Bookmark, Tag +from bookmarks.models import Bookmark, parse_tag_string +from services.tags import get_or_create_tags def import_netscape_html(html: str, user: User): @@ -38,9 +38,9 @@ def _import_bookmark_tag(bookmark_tag: bs4.Tag, user: User): # Set tags tag_string = link_tag['tags'] - tag_names = tag_string.strip().split(',') + tag_names = parse_tag_string(tag_string) + tags = get_or_create_tags(tag_names, user) - tags = [_get_or_create_tag(tag_name, user) for tag_name in tag_names] bookmark.tags.set(tags) bookmark.save() @@ -50,13 +50,3 @@ def _get_or_create_bookmark(url: str, user: User): return Bookmark.objects.get(url=url, owner=user) except Bookmark.DoesNotExist: return Bookmark() - - -def _get_or_create_tag(name: str, user: User): - try: - return Tag.objects.get(name=name, owner=user) - except Tag.DoesNotExist: - tag = Tag(name=name, owner=user) - tag.date_added = timezone.now() - tag.save() - return tag diff --git a/bookmarks/services/tags.py b/bookmarks/services/tags.py new file mode 100644 index 0000000..cc614cf --- /dev/null +++ b/bookmarks/services/tags.py @@ -0,0 +1,19 @@ +from typing import List + +from django.contrib.auth.models import User +from django.utils import timezone + +from bookmarks.models import Tag + +def get_or_create_tags(tag_names: List[str], user: User): + return [get_or_create_tag(tag_name, user) for tag_name in tag_names] + + +def get_or_create_tag(name: str, user: User): + try: + return Tag.objects.get(name=name, owner=user) + except Tag.DoesNotExist: + tag = Tag(name=name, owner=user) + tag.date_added = timezone.now() + tag.save() + return tag diff --git a/bookmarks/templates/bookmarks/form.html b/bookmarks/templates/bookmarks/form.html index 01daa99..1ff922e 100644 --- a/bookmarks/templates/bookmarks/form.html +++ b/bookmarks/templates/bookmarks/form.html @@ -11,13 +11,27 @@ {% endif %}
- - {{ form.title|add_class:"form-input"|attr:"placeholder: Leave empty to fill from website metadata" }} + + {{ form.tag_string|add_class:"form-input" }} +
+ Enter any number of tags separated by space and without the hash (#). If a tag does not exist it will be automatically created. +
+ {{ form.tag_string.errors }} +
+
+ + {{ form.title|add_class:"form-input" }} +
+ Optional, leave empty to use title from website. +
{{ form.title.errors }}
- - {{ form.description|add_class:"form-input"|attr:"placeholder: Leave empty to fill from website metadata" }} + + {{ form.description|add_class:"form-input"|attr:"rows:4" }} +
+ Optional, leave empty to use description from website. +
{{ form.description.errors }}
diff --git a/bookmarks/views/bookmarks.py b/bookmarks/views/bookmarks.py index 4fb1694..4dde1df 100644 --- a/bookmarks/views/bookmarks.py +++ b/bookmarks/views/bookmarks.py @@ -4,7 +4,7 @@ from django.shortcuts import render from django.urls import reverse from bookmarks import queries -from bookmarks.models import Bookmark, BookmarkForm +from bookmarks.models import Bookmark, BookmarkForm, build_tag_string from bookmarks.services.bookmarks import create_bookmark, update_bookmark _default_page_size = 30 @@ -35,9 +35,8 @@ def new(request): if request.method == 'POST': form = BookmarkForm(request.POST) if form.is_valid(): - bookmark = form.save(commit=False) current_user = request.user - create_bookmark(bookmark, current_user) + create_bookmark(form, current_user) return HttpResponseRedirect(reverse('bookmarks:index')) else: form = BookmarkForm() @@ -50,12 +49,12 @@ def edit(request, bookmark_id: int): if request.method == 'POST': form = BookmarkForm(request.POST, instance=bookmark) if form.is_valid(): - bookmark = form.save(commit=False) - update_bookmark(bookmark) + update_bookmark(form, request.user) return HttpResponseRedirect(reverse('bookmarks:index')) else: form = BookmarkForm(instance=bookmark) + form.initial['tag_string'] = build_tag_string(bookmark.tag_names, ' ') return render(request, 'bookmarks/edit.html', {'form': form, 'bookmark_id': bookmark_id})