diff options
-rw-r--r-- | app/notes/admin.py | 34 | ||||
-rw-r--r-- | app/notes/forms.py | 15 | ||||
-rw-r--r-- | app/notes/migrations/0001_initial.py | 30 | ||||
-rw-r--r-- | app/notes/migrations/0002_luxnote_tags.py | 20 | ||||
-rw-r--r-- | app/notes/migrations/__init__.py | 0 | ||||
-rw-r--r-- | app/notes/models.py | 41 | ||||
-rw-r--r-- | app/notes/templates/confirm_delete.html | 12 | ||||
-rw-r--r-- | app/notes/templates/note_form.html | 39 | ||||
-rw-r--r-- | app/notes/templates/note_list.html | 31 | ||||
-rw-r--r-- | app/notes/templates/project_detail.html | 40 | ||||
-rw-r--r-- | app/notes/templates/project_form.html | 35 | ||||
-rw-r--r-- | app/notes/templates/project_list.html | 25 | ||||
-rw-r--r-- | app/notes/urls.py | 34 | ||||
-rw-r--r-- | app/notes/views.py | 72 | ||||
-rw-r--r-- | config/base_urls.py | 1 | ||||
-rw-r--r-- | templates/base_notes.html | 43 |
16 files changed, 472 insertions, 0 deletions
diff --git a/app/notes/admin.py b/app/notes/admin.py new file mode 100644 index 0000000..5409200 --- /dev/null +++ b/app/notes/admin.py @@ -0,0 +1,34 @@ +from django.contrib import admin + +from utils.widgets import AdminImageWidget, LGEntryForm, TagListFilter + +from .models import ( + LuxNote, +) + + +@admin.register(LuxNote) +class LuxNoteAdmin(admin.ModelAdmin): + list_display = ('title', 'admin_link', 'date_created') + search_fields = ['title', 'description', 'url'] + list_filter = [TagListFilter] + fieldsets = ( + (None, { + 'fields': ( + 'title', + 'url', + 'description', + 'body_markdown' + 'tags', + ) + }), + ('Details', { + 'fields': ( + 'date_created ', + ), + 'classes': 'collapse' + }), + ) + + class Media: + js = ('next-prev-links.js',) diff --git a/app/notes/forms.py b/app/notes/forms.py new file mode 100644 index 0000000..72395c6 --- /dev/null +++ b/app/notes/forms.py @@ -0,0 +1,15 @@ +from django.forms import ModelForm + +from .models import LuxNote + + +class LuxNoteCreateForm(ModelForm): + class Meta: + model = LuxNote + fields = ['title', 'url', 'description', 'body_markdown', 'tags' ] + + +class LuxNoteEditForm(ModelForm): + class Meta: + model = LuxNote + fields = ['title', 'url', 'description', 'body_markdown', 'tags' ] diff --git a/app/notes/migrations/0001_initial.py b/app/notes/migrations/0001_initial.py new file mode 100644 index 0000000..deb651d --- /dev/null +++ b/app/notes/migrations/0001_initial.py @@ -0,0 +1,30 @@ +# Generated by Django 5.1.7 on 2025-04-07 16:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='LuxNote', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=200)), + ('url', models.CharField(blank=True, max_length=400, null=True)), + ('description', models.TextField(blank=True, null=True)), + ('body_markdown', models.TextField(blank=True, null=True)), + ('body_html', models.TextField(blank=True)), + ('date_created', models.DateTimeField(auto_now=True)), + ], + options={ + 'ordering': ('-date_created',), + 'get_latest_by': 'date_created', + }, + ), + ] diff --git a/app/notes/migrations/0002_luxnote_tags.py b/app/notes/migrations/0002_luxnote_tags.py new file mode 100644 index 0000000..6cb50be --- /dev/null +++ b/app/notes/migrations/0002_luxnote_tags.py @@ -0,0 +1,20 @@ +# Generated by Django 5.1.7 on 2025-04-07 16:22 + +import taggit.managers +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('notes', '0001_initial'), + ('taggit', '0006_rename_taggeditem_content_type_object_id_taggit_tagg_content_8fc721_idx'), + ] + + operations = [ + migrations.AddField( + model_name='luxnote', + name='tags', + field=taggit.managers.TaggableManager(blank=True, help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'), + ), + ] diff --git a/app/notes/migrations/__init__.py b/app/notes/migrations/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/app/notes/migrations/__init__.py diff --git a/app/notes/models.py b/app/notes/models.py new file mode 100644 index 0000000..c0c88e8 --- /dev/null +++ b/app/notes/models.py @@ -0,0 +1,41 @@ +import datetime + +from django.db import models +from django.urls import reverse +from django.utils import timezone +from django.utils.html import format_html + +from taggit.managers import TaggableManager +from utils.util import markdown_to_html + + +#class LuxProject(models.Model): + +class LuxNote(models.Model): + title = models.CharField(max_length=200) + url = models.CharField(max_length=400, blank=True, null=True) + description = models.TextField(blank=True, null=True) + body_markdown = models.TextField(null=True, blank=True) + body_html = models.TextField(blank=True) + date_created = models.DateTimeField(auto_now=True) + #project = models.ForeignKey(LuxProject, on_delete=models.SET_NULL, null=True, blank=True) + tags = TaggableManager(blank=True) + + class Meta: + ordering = ('-date_created',) + get_latest_by = 'date_created' + + def __str__(self): + return self.title + + def admin_link(self): + return format_html('<a href="%s">Visit Site</a>' % (self.url)) + admin_link.short_description = 'Link' + + def save(self, *args, **kwargs): + if self.body_markdown: + self.body_html = markdown_to_html(self.body_markdown) + super(LuxNote, self).save(*args, **kwargs) + + def get_absolute_url(self, *args, **kwargs): + return reverse('notes:note-edit', kwargs={"pk": self.pk}) diff --git a/app/notes/templates/confirm_delete.html b/app/notes/templates/confirm_delete.html new file mode 100644 index 0000000..8e657b1 --- /dev/null +++ b/app/notes/templates/confirm_delete.html @@ -0,0 +1,12 @@ +<form method="post">{% csrf_token %} + + + + +<p>Are you sure you want to delete "{{ object }}"?</p> + + + + + <input type="submit" value="Confirm"> +</form> diff --git a/app/notes/templates/note_form.html b/app/notes/templates/note_form.html new file mode 100644 index 0000000..9c8ac37 --- /dev/null +++ b/app/notes/templates/note_form.html @@ -0,0 +1,39 @@ +{% extends 'base_notes.html' %} +{% block extrahead %} +<style> +form .selector label { + position: inherit; +} +</style> +<script src="/media/js/nice-select2.js"></script> +<link rel="stylesheet" href="/media/nice-select2.css"> +{% endblock %} +{% block primary %} +<main role="main" class="archive-wrapper"> + <div class="post-body"> + <form action="" method="post" class="comment-form">{% csrf_token %} + {% for field in form %} + <fieldset> + {%if field.name == "project" or field.name == "status" or field.name == 'note_type'%}<span class="selector">{{field.label_tag}}</span>{%else%}{{field.label_tag}}{%endif%} + {%if field.name == "body_markdown" or field.name == "description" %}<div class="textarea-rounded">{{ field }}</div>{%else%}{{field}}{%endif%} + </fieldset> + <small class="alert">{% if field.errors %}{{field.errors}}{% endif %}</small> + {%endfor%} + <input class="btn" type="submit" name="add_new" value="Save and add another" /> + <input type="submit" name="save" class="btn" value="Save" /> + </form> + </div> +</main> +{% endblock %} + {% block js %} +<script type="text/javascript"> +var options = {searchable: true}; +NiceSelect.bind(document.getElementById("id_project"), options); + {% if is_update %}{%else%} +let params = new URL(document.location).searchParams; +document.getElementById('id_title').value = params.get("title"); +document.getElementById('id_url').value = params.get("url"); +document.getElementById('id_body_markdown').value = params.get("description"); +{% endif %} +</script> + {% endblock%} diff --git a/app/notes/templates/note_list.html b/app/notes/templates/note_list.html new file mode 100644 index 0000000..bd16cc0 --- /dev/null +++ b/app/notes/templates/note_list.html @@ -0,0 +1,31 @@ +{% extends 'base_notes.html' %} +{% block primary %} +<main class="post-detail"> + <div class="post-header"><ul class="flex header-list"> + <li><a class="btn" href="{% url 'notes:note-list' %}">All</a></li> + {% for object in note_types %} + <li><a class="btn" href="{% url 'gtd:note-list-status' object.label|lower%}">{{object.label}}</a></li> + {% endfor %} + <li class="right"><a href="{% url 'notes:note-create' %}" class="btn">New Note</a></li> + </ul> + +<select class="form-control" style="margin-top: 2%;" onchange="go_from_select(this.options[this.selectedIndex].value)"> + <option value="">All Projects</option>{% for object in tags %} + <option {% if object.Tag == project %}selected="selected" {%endif%}value="?tag={{object}}">{{object}}</option>{%endfor%} +</select> + </div> + <div class="note-list">{% for object in object_list %}<article> + <h2>{% if object.url %}<a href="{{object.url}}">{{object.title}}</a>{%else%}{{object.title}}{%endif%} <span class="note-edit"><a href="{%url 'gtd:note-edit' object.pk%}">edit</a></span></h2> + <p>{{object.body_markdown}}</p> + {% if object.project %}<p class="small">For: <a href="{% url 'gtd:project-detail' object.project.id %}">{{object.project}}</a></p>{%endif%} + <p class="small">Status: {{object.get_status_display}}</p> + <p class="small"><a href="{% url 'gtd:note-delete' object.pk %}">delete</a></p> + </article> +{% endfor%}</div> +</main> + + +{% endblock %} +{% block js %} +<script type="text/javascript">var go_from_select = function(opt) { window.location = window.location.pathname + opt };</script> +{% endblock%} diff --git a/app/notes/templates/project_detail.html b/app/notes/templates/project_detail.html new file mode 100644 index 0000000..543b9f6 --- /dev/null +++ b/app/notes/templates/project_detail.html @@ -0,0 +1,40 @@ +{% extends 'base_gtd.html' %} +{% load typogrify_tags %} +{% load get_note_type %} +{% block extrahead %} +<style> +.detail-header { + margin-top: 3rem; + margin-bottom: 1rem; + padding-bottom: 1rem; +} +</style> +{% endblock %} +{% block primary %} +<main role="main" class="archive-wrapper"> + <div class="post-header detail-header"> + <h1>Project: {{object.title}}</h1> + +<select class="form-control" style="margin-top: 2%;" onchange="go_from_select(this.options[this.selectedIndex].value)"> + <option value="">All Projects</option>{% for object in projects %} + <option {% if object.title == project %}selected="selected" {%endif%}value="{% url 'gtd:project-detail' object.id%}">{{object}}</option>{%endfor%} +</select> + </div> + <div class="post-body"> + {{object.body_html|smartypants|safe}} + {% regroup note_set by note_type as type_list %} + {% for type in type_list %} + <h4>{% get_note_type type.grouper %}</h4> + <div class="note-list">{% for object in type.list %}<article> + <h2>{% if object.get_status_display == 'Completed' %}<strike style="color: #918d8d">{%endif%}{% if object.url %}<a href="{{object.url}}">{{object.title}}</a>{%else%}{{object.title}}{%endif%} <span class="note-edit"><a href="{%url 'gtd:note-edit' object.pk%}">edit</a></span>{% if object.get_status_display == 'Completed' %}</strike>{%endif%}</h2> + {{object.body_html|smartypants|safe}} + {% if object.get_status_display != 'None' %}<p class="small">{{object.get_status_display}}</p>{% endif %} + <p class="small"><a href="{% url 'gtd:note-delete' object.pk %}">delete</a></p></article> + {% endfor %}</div> +{% endfor %} + </div> +</main> +{% endblock %} +{% block js %} +<script type="text/javascript">var go_from_select = function(opt) { console.log(opt); window.location = opt };</script> +{% endblock%} diff --git a/app/notes/templates/project_form.html b/app/notes/templates/project_form.html new file mode 100644 index 0000000..ac7d13f --- /dev/null +++ b/app/notes/templates/project_form.html @@ -0,0 +1,35 @@ +{% extends 'base_gtd.html' %} + +{% block extrahead %} +<style> +form .selector label { + position: inherit; +} +</style> +{% endblock %} +{% block primary %} +<main role="main" class="archive-wrapper"> + <div class="post-body"> + <form action="" method="post" class="comment-form">{% csrf_token %} + {% for field in form %} + <fieldset> + {%if field.name == "project_type" or field.name == "outcome" or field.name == 'note_type'%}<span class="selector">{{field.label_tag}}</span>{%else%}{{field.label_tag}}{%endif%} + {%if field.name == "body_markdown"%}<div class="textarea-rounded">{{ field }}</div>{%else%}{{field}}{%endif%} + </fieldset> + <small class="alert">{% if field.errors %}{{field.errors}}{% endif %}</small> + {%endfor%} + <input class="btn" type="submit" name="add_new" value="Save and add another" /> + <input type="submit" name="save" class="btn" value="Save" /> + </form> + </div> +</main> +{% endblock %} + {% block js %} + {% if is_update %}{%else%} +<script type="text/javascript"> +let params = new URL(document.location).searchParams; +document.getElementById('id_title').value = params.get("title"); +document.getElementById('id_body_markdown').value = params.get("description"); +</script> +{% endif %} + {% endblock%} diff --git a/app/notes/templates/project_list.html b/app/notes/templates/project_list.html new file mode 100644 index 0000000..ba46b59 --- /dev/null +++ b/app/notes/templates/project_list.html @@ -0,0 +1,25 @@ +{% extends 'base_gtd.html' %} +{% block primary %} +<main class="post-detail"> + <div class="post-header"><ul class="flex header-list"> + <li><a class="btn" href="{% url 'gtd:project-list' %}">All</a></li> + {% for object in project_types %} + <li><a class="btn" href="{% url 'gtd:project-list-type' object.1|lower%}">{%if object.1 == 'Lbh'%}{{object.1|upper}}{%else%}{{object.1}}{%endif%}</a></li> + {% endfor %} + <li class="right"><a href="{% url 'gtd:project-create' %}" class="btn">New</a></li> + </ul> + </div> + <div class="note-list">{% for object in object_list %}<article> + <h2><a href="{{object.get_absolute_url}}">{{object.title}}</a><span class="note-edit"><a href="{% url 'gtd:project-edit' object.pk %}">edit</a></span></h2> + <p>{{object.body_markdown}}</p> + <p class="small">Date Goal: {{object.date_goal}}</p> + <p class="small">Type: {{object.get_project_type_display}}</p> + <p class="small"><a href="{% url 'gtd:project-delete' object.pk %}">delete</a></p> + </article> +{% endfor%}</div> +</main> + + +{% endblock %} +{% block js %} +{% endblock%} diff --git a/app/notes/urls.py b/app/notes/urls.py new file mode 100644 index 0000000..eb83804 --- /dev/null +++ b/app/notes/urls.py @@ -0,0 +1,34 @@ +from django.urls import path + +from . import views + +app_name = "notes" + +urlpatterns = [ + path( + r'', + views.LuxNoteListView.as_view(), + {'note_type': None}, + name="note-list" + ), + path( + r'create', + views.LuxNoteCreateView.as_view(), + name="note-create" + ), + path( + r'<str:note_type>', + views.LuxNoteListView.as_view(), + name="note-list-status" + ), + path( + r'<pk>/edit', + views.LuxNoteUpdateView.as_view(), + name="note-edit" + ), + path( + r'<pk>/delete', + views.LuxNoteDeleteView.as_view(), + name="note-delete" + ), +] diff --git a/app/notes/views.py b/app/notes/views.py new file mode 100644 index 0000000..b160bed --- /dev/null +++ b/app/notes/views.py @@ -0,0 +1,72 @@ +from django.views.generic import UpdateView, DetailView, ListView, CreateView, DeleteView, RedirectView +from django.views.generic.base import TemplateView +from django.urls import reverse, reverse_lazy +from django.db.models import Q +from django.db.models import Count + +#from taxonomy.models import Category + +from .models import ( + LuxNote, +) + +from .forms import ( + LuxNoteCreateForm, + LuxNoteEditForm, +) + + +class LuxNoteCreateView(CreateView): + model = LuxNote + form_class = LuxNoteCreateForm + template_name = "note_form.html" + + def get_success_url(self): + if 'add_new' in self.request.POST: + return reverse('notes:note-create') + else: + if self.object.project: + return reverse('notes:project-detail', kwargs={"pk": self.object.project.pk}) + else: + return reverse('notes:note-create') + + +class LuxNoteUpdateView(UpdateView): + model = LuxNote + form_class = LuxNoteEditForm + template_name = "note_form.html" + + def get_context_data(self, **kwargs): + context = super(LuxNoteUpdateView, self).get_context_data(**kwargs) + context['is_update'] = True + return context + + def get_success_url(self): + if self.object.project: + return reverse('notes:project-detail', kwargs={"pk": self.object.project.pk}) + else: + return reverse('notes:note-list', kwargs={"pk": self.object.project.pk}) + + +class LuxNoteListView(ListView): + model = LuxNote + template_name = "note_list.html" + + def get_queryset(self): + tag = self.request.GET.get("tag", False) + if tag: + return LuxNote.objects.filter(tags__name__in=[tag]) + return LuxNote.objects.all() + + def get_context_data(self, **kwargs): + context = super(LuxNoteListView, self).get_context_data(**kwargs) + context['tags'] = LuxNote.tags.all().order_by('name') + #context['project'] = self.request.GET.get("project", False) + return context + + +class LuxNoteDeleteView(DeleteView): + # specify the model you want to use + model = LuxNote + success_url = "/notes/notes" + template_name = "notes/confirm_delete.html" diff --git a/config/base_urls.py b/config/base_urls.py index 93b90b7..ad79987 100644 --- a/config/base_urls.py +++ b/config/base_urls.py @@ -56,6 +56,7 @@ urlpatterns = [ path(r'admin/', admin.site.urls), path(r'trading/', include('trading.urls')), path(r'gtd/', include('gtd.urls')), + path(r'notes/', include('notes.urls')), path(r'planner/', include('planner.urls')), path(r'luximages/insert/', utils.views.insert_image), path(r'feed.xml', JrnlRSSFeedView(),name="feed"), diff --git a/templates/base_notes.html b/templates/base_notes.html new file mode 100644 index 0000000..f406446 --- /dev/null +++ b/templates/base_notes.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<html {%block htmlclass%}{%endblock%} dir="ltr" lang="en-US"> + {% block sitename %} +<head> + <title>{% block pagetitle %}{% endblock %}</title>{%endblock%} + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <meta name="description" + content="{% block metadescription %}{% endblock %}"> + <meta name="author" content="luxagraf"> + {%block stylesheet%}<link rel="stylesheet" + href="/media/gtdscreenv1.css{%comment%}?{% now "u" %}{%endcomment%}" + media="screen">{%endblock%} + <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon"> + {%block extrahead%}{%endblock%} +</head> +<body {%block bodyid%}{%endblock%}{%block bodyevents%}{%endblock%}> + <header class="header-wrapper"> + <div id="logo"> + <a class="logo-link" href="/" title="Home">L</span></a> + </div> + <nav> + <a class="nav-item" href="{% url 'notes:note-list' %}" title="View Notes">Notes</a> + <a class="nav-item" href="{% url 'notes:note-list' %}" title="View things that need to be done">todo</a> + </nav> + </header> + {% block breadcrumbs %}{% endblock %} + {% block primary %}{% endblock %} + {% block extrabody %}{% endblock %} + <footer class="page-footer"> + <nav> + <a class="nav-item" href="/notes/todo" title="See what needs to be called in">Todo</a> + <a class="nav-item" href="/notes" title="View Guides">Notes</a> + <a class="nav-item" href="/posts" title="View Guides">Guides</a> + </nav> + <p id="license"> + © 2020-{% now "Y" %} + <span class="h-card"><a class="p-name u-url" href="https://luxagraf.net/">luxagraf</a><data class="p-locality" value="Everywhere"></data><data class="p-country-name" value="United States"></data></span>. + </p> + </footer> + {% block js %}{% endblock%} +</body> +</html> |