2024-12-20-Friday
created: 2024-12-20 05:24 tags: - daily-notes
Friday, December 20, 2024
<< Timestamps/2024/12-December/2024-12-19-Thursday|Yesterday | Timestamps/2024/12-December/2024-12-21-Saturday|Tomorrow >>
🎯 Goal
- [x] Develop Django Management Commands that can be run on these different
Vault
s to help parse information from the Markdown files and load them directly into the Django Model's data (mainly summaries and tags).
🌟 Results
- Created a dynamic Django Management Command that can tag notes based on their
Vault
and any number of substrings found in aNote
'srelative_path
structure - Updated the Django View to display only notes tagged as
Daily Note
s
🌱 Next Time
- Schedule the task to update notes as daily notes or make it part of the import process
- Make sure relative paths of notes are changed when their location changes in the Local Version of my obsidian notes.
📝 Notes
Now that I have the structure for the Note
s application of my BigBrain App, I wanted to do three primary things:
- Tag my daily notes as daily notes,
- Filter and sort the
VaultNoteListView
Django View to display only daily notes, and - Extract summary information from notes to input in the "summary" section of the note cards displayed in the
VaultNoteListView
I already store the relative path as an Attribute of each Note
Django Model, so getting the notes that should be tagged as daily will just be a matter of formatting based on the vault they come from (I.e daily notes here are under the Timestamps
folder at the front of the relative path, daily notes in my WGU MSDADS program are tagged under /Projects/Daily Notes/
where I should probably just check for the /Daily Notes/
substring in the relative path. I should also make sure to update the relative paths of the notes / make sure those are updated on import. I think the only thing I use to determine whether a note has changed currently is the content.
Either way the first thing to do was to start a create a tag for Daily Notes
. I can use a Django Management Command to execute this on a neat Django QuerySet:
vault.notes.filter(relative_path__icontains=substring).exclude(tags__name="Daily Note")
which in one line selects all of the notes in a vault containing a given substring in their relative path, excluding those that are already tagged as daily notes. These notes are then updated with the Daily Note
tag so they can be selected specifically and displayed in the Django View later. I also added logic to handle notes that were untagged (maybe a rouge note was found in a daily notes path). Overall the full command is seen here:
from django.core.management.base import BaseCommand
from apps.bigbrain.models import Note, Vault, Tag
from django.db.models import Q
class Command(BaseCommand):
help = """Associates or dissociates the 'Daily Note' tag for notes in the database
based on their relative_path attribute.
"""
def add_arguments(self, parser):
parser.add_argument(
"--vault-slug",
type=str,
help="Slug of the vault that you want to update 'Daily Note' tags for",
)
def handle(self, *args, **kwargs):
# Extract the desired vault based on its slug
vault_slug = kwargs["vault_slug"]
try:
vault = Vault.objects.get(slug=vault_slug)
self.stdout.write(
self.style.SUCCESS(f"Accessing vault: {vault.name}...")
)
except Vault.DoesNotExist:
self.stdout.write(
self.style.ERROR(f"ERROR: Vault slug not found: {vault_slug}")
)
return
# Create the "Daily Note" tag if it does not exist
daily_note_tag, created = Tag.objects.get_or_create(name="Daily Note")
if created:
self.stdout.write(self.style.SUCCESS("Created 'Daily Note' tag."))
# Define substrings to identify daily notes based on the vault slug
note_keys = {
"dimmin-notes": ("oldNotes", "Timestamps"),
"wgu-msdads": ("Daily Notes",),
}
# Check if the vault slug exists in the note keys
if vault_slug not in note_keys:
self.stdout.write(
self.style.ERROR(f"No substrings defined for vault slug: {vault_slug}")
)
return
# Get the substrings to filter notes
substring_indicators = note_keys[vault_slug]
# Step 1: Add the "Daily Note" tag to matching notes
for substring in substring_indicators:
notes_to_update = (
vault.notes.filter(relative_path__icontains=substring)
.exclude(tags__name="Daily Note")
)
self.stdout.write(
f"Found {notes_to_update.count()} notes with substring '{substring}' to tag."
)
for note in notes_to_update:
note.tags.add(daily_note_tag)
note.save()
self.stdout.write(self.style.SUCCESS(f"Tagged note: {note.title}"))
# Step 2: Remove the "Daily Note" tag from notes that no longer match
# Combine all substring filters into a single Q object for efficiency
substring_filters = Q()
for substring in substring_indicators:
substring_filters |= Q(relative_path__icontains=substring)
# Identify notes currently tagged as "Daily Note" but no longer matching any substrings
notes_to_untag = vault.notes.filter(tags__name="Daily Note").exclude(substring_filters)
self.stdout.write(
f"Found {notes_to_untag.count()} notes to untag."
)
for note in notes_to_untag:
note.tags.remove(daily_note_tag)
note.save()
self.stdout.write(self.style.SUCCESS(f"Removed 'Daily Note' tag from note: {note.title}"))
self.stdout.write(self.style.SUCCESS("All applicable notes updated."))
where we can specify what substrings indicate what tag in the note_keys
dictionary. I then extended this further to other Tag
s and keys (i.e we set the highest level key to the new tag we want to get_or_create()
) by slightly modifying the structure of the note_keys
:
note_keys = {
"dimmin-notes": {
"Daily Note": ("oldNotes", "Timestamps"),
},
"wgu-msdads": {
"Daily Note": ("Daily Notes")
},
}
This way any arbitrary number of different tags could be assigned based on some position in the relative_path
of the note (not just Daily Note
but also maybe Algorithms
or Data Science
, etc. ). This more flexible extension is seen here:
from django.core.management.base import BaseCommand
from apps.bigbrain.models import Note, Vault, Tag
from django.db.models import Q
class Command(BaseCommand):
help = """Associates or dissociates tags for notes in the database
based on their relative_path attribute and a flexible structure for tag rules.
"""
def add_arguments(self, parser):
parser.add_argument(
"--vault-slug",
type=str,
help="Slug of the vault that you want to update tags for",
)
def handle(self, *args, **kwargs):
# Extract the desired vault based on its slug
vault_slug = kwargs["vault_slug"]
try:
vault = Vault.objects.get(slug=vault_slug)
self.stdout.write(self.style.SUCCESS(f"Accessing vault: {vault.name}..."))
except Vault.DoesNotExist:
self.stdout.write(self.style.ERROR(f"ERROR: Vault slug not found: {vault_slug}"))
return
# Define the rules for tagging based on substrings in relative_path
note_keys = {
"dimmin-notes": {
"Daily Note": ("oldNotes", "Timestamps"),
"Archive": ("Archived", "Backup"),
},
"wgu-msdads": {
"Daily Note": ("Daily Notes",),
"Research": ("Research", "Studies"),
},
}
# Check if the vault slug exists in the note keys
if vault_slug not in note_keys:
self.stdout.write(
self.style.ERROR(f"No tag rules defined for vault slug: {vault_slug}")
)
return
# Get the tag rules for this vault
tag_rules = note_keys[vault_slug]
for tag_name, substrings in tag_rules.items():
# Get or create the tag
tag, created = Tag.objects.get_or_create(name=tag_name)
if created:
self.stdout.write(self.style.SUCCESS(f"Created tag: '{tag_name}'"))
# Step 1: Add the tag to notes matching the substrings
for substring in substrings:
notes_to_update = (
vault.notes.filter(relative_path__icontains=substring)
.exclude(tags__name=tag_name)
)
self.stdout.write(
f"Found {notes_to_update.count()} notes with substring '{substring}' to tag with '{tag_name}'."
)
for note in notes_to_update:
note.tags.add(tag)
note.save()
self.stdout.write(self.style.SUCCESS(f"Tagged note: {note.title} with '{tag_name}'"))
# Step 2: Remove the tag from notes that no longer match any substrings
# Combine all substring filters into a single Q object for efficiency
substring_filters = Q()
for substring in substrings:
substring_filters |= Q(relative_path__icontains=substring)
# Identify notes currently tagged but no longer matching any substrings
notes_to_untag = vault.notes.filter(tags__name=tag_name).exclude(substring_filters)
self.stdout.write(
f"Found {notes_to_untag.count()} notes to untag for '{tag_name}'."
)
for note in notes_to_untag:
note.tags.remove(tag)
note.save()
self.stdout.write(self.style.SUCCESS(f"Removed tag '{tag_name}' from note: {note.title}"))
self.stdout.write(self.style.SUCCESS("All applicable tags updated."))
Now I could tag different notes as Archive
or Research
etc. based on its relative path. However for now I'll just use the Daily Note
feature because it's most relevant to me.
Next I needed to update the Django View to only display notes tagged as a Daily Note
. I updated the VaultNoteListView
to add a filter on the original Django QuerySet to include only notes that are tagged with Daily Note
in the display, and similarly for the end of the note_detail
page I'll want to be able to navigate between the next day's note and the previous day's note. There's a slight issue where I can't look for the title of the note and instead filter by created by which can cause some issues later down the line, but it's so minor that I'll make a note of it as a Github Issue (Properly Sort Daily Notes by Title) and move on. I still need to add this as a scheduled task / add it in to the workflow of the notes database but overall it's looking pretty solid.
I committed these changes with Git and pushed to Production.
Notes created today
List FROM "" WHERE file.cday = date("2024-12-20") SORT file.ctime asc
Notes last touched today
List FROM "" WHERE file.mday = date("2024-12-20") SORT file.mtime asc