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


🌟 Results

  • Created a dynamic Django Management Command that can tag notes based on their Vault and any number of substrings found in a Note's relative_path structure
  • Updated the Django View to display only notes tagged as Daily Notes

🌱 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 Notes 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 Tags 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

(Template referenced from Dann Berg, can be found here)


Previous Note 2024-12-19-Thursday Next Note 2024-12-21-Saturday