diff options
author | luxagraf <sng@luxagraf.net> | 2018-12-29 08:37:39 -0600 |
---|---|---|
committer | luxagraf <sng@luxagraf.net> | 2018-12-29 08:37:39 -0600 |
commit | 4f7b84194b056b5d6d9acca4cceb2cabc04fd8a5 (patch) | |
tree | 7fe109e7aeaddab7aa5e7f46f99414064a248e52 /apps | |
parent | 02f520038e3c6d5a01c9545e9b1c3eb91e4e016c (diff) |
cleaned up JS and made modal handler.
Diffstat (limited to 'apps')
21 files changed, 545 insertions, 66 deletions
diff --git a/apps/notes/admin.py b/apps/notes/admin.py index 3958d55..44915f2 100644 --- a/apps/notes/admin.py +++ b/apps/notes/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin -from .models import Note, Notebook +from .models import Note, Notebook, LuxTag @admin.register(Note) @@ -8,6 +8,11 @@ class NoteAdmin(admin.ModelAdmin): pass +@admin.register(LuxTag) +class TagAdmin(admin.ModelAdmin): + pass + + @admin.register(Notebook) class NotebookAdmin(admin.ModelAdmin): pass diff --git a/apps/notes/forms.py b/apps/notes/forms.py index 5ef9f84..4dc79d2 100644 --- a/apps/notes/forms.py +++ b/apps/notes/forms.py @@ -1,10 +1,19 @@ from django import forms from django.utils.translation import ugettext_lazy as _ +from django.core.exceptions import NON_FIELD_ERRORS + +from utils.widgets import RelatedFieldWidgetCanAdd from .models import Note, Notebook -class NoteForm(forms.ModelForm): +class BaseNoteForm(forms.ModelForm): + def __init__(self, *args, **kwargs): + self.user = kwargs.pop("user", None) + super(BaseNoteForm, self).__init__(*args, **kwargs) + + +class NoteForm(BaseNoteForm): class Meta: model = Note fields = ['title', 'body_text', 'body_html', 'body_qjson', 'notebook', 'url', 'tags'] @@ -13,14 +22,28 @@ class NoteForm(forms.ModelForm): } def __init__(self, *args, **kwargs): - self.user = kwargs.pop("user", None) + user = kwargs.pop("user", None) super(NoteForm, self).__init__(*args, **kwargs) + self.fields['notebook'].widget = RelatedFieldWidgetCanAdd(Notebook, related_url="notebooks:list") + self.fields['notebook'].queryset = Notebook.objects.filter(owner__username=user) -class NotebookForm(NoteForm): +class NotebookForm(BaseNoteForm): class Meta: model = Notebook - fields = ['name', 'color_rgb'] + fields = ['owner', 'name', 'color_rgb'] + widgets = {'owner': forms.HiddenInput()} labels = { "name": _("Notebook Name"), + "color_rgb": _("Notebook Color"), + } + error_messages = { + NON_FIELD_ERRORS: { + 'unique_together': "You already have a notebook by that name, please choose a different name", + } } + + def __init__(self, *args, **kwargs): + user = kwargs.pop("user", None) + super(NotebookForm, self).__init__(*args, **kwargs) + self.fields['owner'].initial = user diff --git a/apps/notes/migrations/0002_auto_20181204_0620.py b/apps/notes/migrations/0002_auto_20181204_0620.py new file mode 100644 index 0000000..e7cb38d --- /dev/null +++ b/apps/notes/migrations/0002_auto_20181204_0620.py @@ -0,0 +1,42 @@ +# Generated by Django 2.1.2 on 2018-12-04 12:20 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('notes', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='Tag', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=40)), + ('slug', models.SlugField(blank=True)), + ('color_hex', models.CharField(max_length=6)), + ('date_created', models.DateTimeField(auto_now_add=True)), + ('date_updated', models.DateTimeField(auto_now=True)), + ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='parent_tag', to='notes.Tag')), + ], + ), + migrations.AlterModelOptions( + name='note', + options={'ordering': ('-date_created', '-date_updated')}, + ), + migrations.AddField( + model_name='note', + name='tagstwo', + field=models.ManyToManyField(blank=True, to='notes.Tag'), + ), + migrations.AlterUniqueTogether( + name='tag', + unique_together={('owner', 'name')}, + ), + ] diff --git a/apps/notes/migrations/0003_auto_20181204_0641.py b/apps/notes/migrations/0003_auto_20181204_0641.py new file mode 100644 index 0000000..9423058 --- /dev/null +++ b/apps/notes/migrations/0003_auto_20181204_0641.py @@ -0,0 +1,59 @@ +# Generated by Django 2.1.2 on 2018-12-04 12:41 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('notes', '0002_auto_20181204_0620'), + ] + + operations = [ + migrations.CreateModel( + name='LuxTag', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100, unique=True, verbose_name='Name')), + ('slug', models.SlugField(max_length=100, unique=True, verbose_name='Slug')), + ('color_hex', models.CharField(max_length=6)), + ], + options={ + 'verbose_name': 'Tag', + 'verbose_name_plural': 'Tags', + }, + ), + migrations.CreateModel( + name='TaggedNotes', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('object_id', models.IntegerField(db_index=True, verbose_name='Object id')), + ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notes_taggednotes_tagged_items', to='contenttypes.ContentType', verbose_name='Content type')), + ('tag', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notes_taggednotes_items', to='notes.LuxTag')), + ], + options={ + 'abstract': False, + }, + ), + migrations.AlterUniqueTogether( + name='tag', + unique_together=set(), + ), + migrations.RemoveField( + model_name='tag', + name='owner', + ), + migrations.RemoveField( + model_name='tag', + name='parent', + ), + migrations.RemoveField( + model_name='note', + name='tagstwo', + ), + migrations.DeleteModel( + name='Tag', + ), + ] diff --git a/apps/notes/migrations/0004_auto_20181204_0653.py b/apps/notes/migrations/0004_auto_20181204_0653.py new file mode 100644 index 0000000..fc6d911 --- /dev/null +++ b/apps/notes/migrations/0004_auto_20181204_0653.py @@ -0,0 +1,19 @@ +# Generated by Django 2.1.2 on 2018-12-04 12:53 + +from django.db import migrations +import taggit.managers + + +class Migration(migrations.Migration): + + dependencies = [ + ('notes', '0003_auto_20181204_0641'), + ] + + operations = [ + migrations.AlterField( + model_name='note', + name='tags', + field=taggit.managers.TaggableManager(blank=True, help_text='Tags', through='notes.TaggedNotes', to='notes.LuxTag', verbose_name='Tags'), + ), + ] diff --git a/apps/notes/migrations/0005_luxtag_owner.py b/apps/notes/migrations/0005_luxtag_owner.py new file mode 100644 index 0000000..168bd0b --- /dev/null +++ b/apps/notes/migrations/0005_luxtag_owner.py @@ -0,0 +1,22 @@ +# Generated by Django 2.1.2 on 2018-12-04 13:06 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('notes', '0004_auto_20181204_0653'), + ] + + operations = [ + migrations.AddField( + model_name='luxtag', + name='owner', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + preserve_default=False, + ), + ] diff --git a/apps/notes/migrations/0006_auto_20181204_0957.py b/apps/notes/migrations/0006_auto_20181204_0957.py new file mode 100644 index 0000000..bf4293c --- /dev/null +++ b/apps/notes/migrations/0006_auto_20181204_0957.py @@ -0,0 +1,22 @@ +# Generated by Django 2.1.2 on 2018-12-04 15:57 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('notes', '0005_luxtag_owner'), + ] + + operations = [ + migrations.RemoveField( + model_name='notebook', + name='url', + ), + migrations.AddField( + model_name='notebook', + name='color_hex', + field=models.CharField(blank=True, max_length=6, null=True), + ), + ] diff --git a/apps/notes/migrations/0007_auto_20181204_1050.py b/apps/notes/migrations/0007_auto_20181204_1050.py new file mode 100644 index 0000000..a4bdc30 --- /dev/null +++ b/apps/notes/migrations/0007_auto_20181204_1050.py @@ -0,0 +1,22 @@ +# Generated by Django 2.1.2 on 2018-12-04 16:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('notes', '0006_auto_20181204_0957'), + ] + + operations = [ + migrations.RemoveField( + model_name='notebook', + name='color_hex', + ), + migrations.AddField( + model_name='notebook', + name='color_rgb', + field=models.CharField(blank=True, max_length=20, null=True), + ), + ] diff --git a/apps/notes/migrations/0008_auto_20181204_1311.py b/apps/notes/migrations/0008_auto_20181204_1311.py new file mode 100644 index 0000000..02bf272 --- /dev/null +++ b/apps/notes/migrations/0008_auto_20181204_1311.py @@ -0,0 +1,19 @@ +# Generated by Django 2.1.2 on 2018-12-04 19:11 + +from django.conf import settings +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('notes', '0007_auto_20181204_1050'), + ] + + operations = [ + migrations.AlterUniqueTogether( + name='notebook', + unique_together={('owner', 'name')}, + ), + ] diff --git a/apps/notes/migrations/0009_remove_luxtag_owner.py b/apps/notes/migrations/0009_remove_luxtag_owner.py new file mode 100644 index 0000000..18896f3 --- /dev/null +++ b/apps/notes/migrations/0009_remove_luxtag_owner.py @@ -0,0 +1,17 @@ +# Generated by Django 2.1.2 on 2018-12-05 03:15 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('notes', '0008_auto_20181204_1311'), + ] + + operations = [ + migrations.RemoveField( + model_name='luxtag', + name='owner', + ), + ] diff --git a/apps/notes/migrations/0010_auto_20181204_2117.py b/apps/notes/migrations/0010_auto_20181204_2117.py new file mode 100644 index 0000000..69da825 --- /dev/null +++ b/apps/notes/migrations/0010_auto_20181204_2117.py @@ -0,0 +1,22 @@ +# Generated by Django 2.1.2 on 2018-12-05 03:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('notes', '0009_remove_luxtag_owner'), + ] + + operations = [ + migrations.RemoveField( + model_name='luxtag', + name='color_hex', + ), + migrations.AddField( + model_name='luxtag', + name='color_rgb', + field=models.CharField(blank=True, max_length=20, null=True), + ), + ] diff --git a/apps/notes/migrations/0011_auto_20181221_1029.py b/apps/notes/migrations/0011_auto_20181221_1029.py new file mode 100644 index 0000000..7b88a62 --- /dev/null +++ b/apps/notes/migrations/0011_auto_20181221_1029.py @@ -0,0 +1,39 @@ +# Generated by Django 2.1.2 on 2018-12-21 16:29 + +from django.conf import settings +import django.contrib.postgres.fields.jsonb +from django.db import migrations, models +import django.db.models.deletion +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('notes', '0010_auto_20181204_2117'), + ] + + operations = [ + migrations.CreateModel( + name='Annotation', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('unique_id', models.UUIDField(default=uuid.uuid4, editable=False)), + ('date_created', models.DateTimeField(auto_now_add=True)), + ('date_updated', models.DateTimeField(auto_now=True)), + ('highlight_text', models.TextField(null=True)), + ('body_text', models.TextField(null=True)), + ('body_html', models.TextField(blank=True, null=True)), + ('body_qjson', django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True)), + ('is_public', models.BooleanField(default=False)), + ('note', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='notes.Note')), + ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.AddField( + model_name='luxtag', + name='owner', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/apps/notes/migrations/0012_auto_20181221_1038.py b/apps/notes/migrations/0012_auto_20181221_1038.py new file mode 100644 index 0000000..f02cbeb --- /dev/null +++ b/apps/notes/migrations/0012_auto_20181221_1038.py @@ -0,0 +1,22 @@ +# Generated by Django 2.1.2 on 2018-12-21 16:38 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +from django.contrib import auth +User = auth.get_user_model() + +class Migration(migrations.Migration): + + dependencies = [ + ('notes', '0011_auto_20181221_1029'), + ] + + operations = [ + migrations.AlterField( + model_name='luxtag', + name='owner', + field=models.ForeignKey(default=User.objects.get(username='luxagraf').id, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + preserve_default=False, + ), + ] diff --git a/apps/notes/migrations/0013_remove_luxtag_owner.py b/apps/notes/migrations/0013_remove_luxtag_owner.py new file mode 100644 index 0000000..a96b105 --- /dev/null +++ b/apps/notes/migrations/0013_remove_luxtag_owner.py @@ -0,0 +1,17 @@ +# Generated by Django 2.1.2 on 2018-12-21 17:20 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('notes', '0012_auto_20181221_1038'), + ] + + operations = [ + migrations.RemoveField( + model_name='luxtag', + name='owner', + ), + ] diff --git a/apps/notes/models.py b/apps/notes/models.py index 95c1c96..9dc4f13 100644 --- a/apps/notes/models.py +++ b/apps/notes/models.py @@ -25,6 +25,10 @@ class LuxTag(TagBase): verbose_name = _("Tag") verbose_name_plural = _("Tags") + @cached_property + def get_absolute_url(self): + return reverse("notes:tags", kwargs={"slug": self.slug}) + class TaggedNotes(GenericTaggedItemBase): tag = models.ForeignKey(LuxTag, related_name="%(app_label)s_%(class)s_items", on_delete=models.CASCADE) @@ -55,6 +59,15 @@ class Notebook(models.Model): def get_absolute_url(self): return reverse("notebooks:detail", kwargs={"slug": self.slug}) + @cached_property + def color_rgba(self, opacity=".5"): + try: + color = self.color_rgb.split('(')[1].split(')')[0] + rgba = "rgba(%s,%s)" % (color, opacity) + except AttributeError: + rgba = self.color_rgb + return rgba + class Note(models.Model): unique_id = models.UUIDField(default=uuid.uuid4, editable=False) @@ -88,3 +101,19 @@ class Note(models.Model): if self._state.adding: self.slug = unique_slug_generator(self) super(Note, self).save() + + +class Annotation(models.Model): + unique_id = models.UUIDField(default=uuid.uuid4, editable=False) + owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) + date_created = models.DateTimeField(blank=True, auto_now_add=True, editable=False) + date_updated = models.DateTimeField(blank=True, auto_now=True, editable=False) + highlight_text = models.TextField(null=True) + body_text = models.TextField(null=True) + body_html = models.TextField(null=True, blank=True) + body_qjson = JSONField(null=True, blank=True) + note = models.ForeignKey(Note, null=True, blank=True, on_delete=models.SET_NULL) + is_public = models.BooleanField(default=False) + + def __str__(self): + return self.body_text[:30] diff --git a/apps/notes/notes_urls.py b/apps/notes/notes_urls.py index f2573ce..55fb32b 100644 --- a/apps/notes/notes_urls.py +++ b/apps/notes/notes_urls.py @@ -11,7 +11,7 @@ app_name = "notes" urlpatterns = [ path(r'create/', NoteCreateView.as_view(), name='create',), - path(r'<slug>/<pk>', NoteDetailView.as_view(), name='detail',), path(r't/<slug>', NoteTagView.as_view(), name='tags',), + path(r'<slug>/<pk>', NoteDetailView.as_view(), name='detail',), path(r'', NoteListView.as_view(), name='list',), ] diff --git a/apps/notes/serializers.py b/apps/notes/serializers.py index f811edd..6bb08de 100644 --- a/apps/notes/serializers.py +++ b/apps/notes/serializers.py @@ -27,7 +27,7 @@ class NoteSerializer(TaggitSerializer, serializers.ModelSerializer): class Meta: model = Note - fields = ('title', 'body_text', 'body_qjson', 'body_html', 'url', 'notebook', 'tags') + fields = ('id', 'title', 'body_text', 'body_qjson', 'body_html', 'url', 'notebook', 'tags') class NotebookSerializer(serializers.HyperlinkedModelSerializer): @@ -38,7 +38,7 @@ class NotebookSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Notebook - fields = ('name', 'color_rgb', 'json_absolute_url', 'owner') + fields = ('id', 'name', 'color_rgb', 'json_absolute_url', 'owner') class NoteTagSerializer(serializers.HyperlinkedModelSerializer): diff --git a/apps/notes/tests/test_models.py b/apps/notes/tests/test_models.py index 05f2618..0c53a25 100644 --- a/apps/notes/tests/test_models.py +++ b/apps/notes/tests/test_models.py @@ -3,7 +3,7 @@ from django.urls import reverse from django.contrib import auth from mixer.backend.django import mixer -from notes.models import Note, Notebook +from notes.models import Note, Notebook User = auth.get_user_model() diff --git a/apps/notes/views.py b/apps/notes/views.py index 5d55720..6751340 100644 --- a/apps/notes/views.py +++ b/apps/notes/views.py @@ -1,6 +1,11 @@ from django.views.generic import CreateView, ListView, UpdateView, DeleteView from django.views.generic.detail import DetailView -from django.views.generic.base import View, RedirectView +from django.views.generic.edit import FormView, ModelFormMixin +from django.http import JsonResponse +from django.core import serializers +from django.forms import modelformset_factory +from django.db.models import Count +from django.views.generic.base import RedirectView from django.utils.decorators import method_decorator from django.contrib.auth.decorators import login_required from django.shortcuts import get_object_or_404, render, redirect @@ -14,10 +19,25 @@ from rest_framework import permissions from .serializers import NoteSerializer, NotebookSerializer, NoteTagSerializer from .models import Note, Notebook, LuxTag from .forms import NoteForm, NotebookForm +from utils.views import AjaxableResponseMixin + +################## +# Base Views +################## + + +@method_decorator(login_required, name='dispatch') +class BaseListView(ListView): + pass + + +@method_decorator(login_required, name='dispatch') +class BaseDetailView(DetailView): + pass @method_decorator(login_required, name='dispatch') -class LoggedInViewWithUser(View): +class LoggedInViewWithUser(FormView): def get_form_kwargs(self, **kwargs): kwargs = super().get_form_kwargs(**kwargs) @@ -25,12 +45,23 @@ class LoggedInViewWithUser(View): return kwargs -class NoteListView(LoggedInViewWithUser, ListView): +################## +# Note Views +################## + + +class NoteListView(BaseListView): model = Note def get_queryset(self): if not self.request.user.is_anonymous: - return Note.objects.filter(owner=self.request.user) + return Note.objects.prefetch_related('tags').filter(owner=self.request.user).select_related('notebook') + + def get_context_data(self, **kwargs): + context = super(NoteListView, self).get_context_data(**kwargs) + context['notebook_list'] = Notebook.objects.filter(owner=self.request.user).exclude(name="Trash").annotate(note_count=Count('note')) + context['tag_list'] = LuxTag.objects.filter(note__owner=self.request.user).annotate(note_count=Count('note')) + return context def get_template_names(self): # print("IP Address for debug-toolbar: " + self.request.META['REMOTE_ADDR']) @@ -40,36 +71,10 @@ class NoteListView(LoggedInViewWithUser, ListView): return ['sell.html'] -class NoteTagView(LoggedInViewWithUser, ListView): - model = Note - template_name = 'notes/notes_list.html' - - def get_queryset(self): - ''' - This can generate a crazy amount of joins if there's a lot of tags - have to keep an eye on it. Would be better to do: - from django.db.models import Q - from functools import reduce - from operator import and_, or_ - #query = reduce(and_, (Q(tags__slug=t) for t in self.tag_list)) - # Note.objects.filter(query, owner=self.request.user) - But that doesn't work for some reason. - ''' - if not self.request.user.is_anonymous: - self.tag_list = [x.strip() for x in self.kwargs['slug'].split("+")] - qs = Note.objects.filter(owner=self.request.user) - for tag in self.tag_list: - qs = qs.filter(tags__slug=tag) - return qs - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context['notes_list'] = Note.objects.filter(owner=self.request.user) - context['tags'] = self.tag_list - return context - - -class NoteDetailView(UpdateView, LoggedInViewWithUser): +class NoteDetailView(LoggedInViewWithUser, AjaxableResponseMixin, UpdateView): + ''' + POST only works as AJAX + ''' model = Note form_class = NoteForm template_name = 'notes/notes_detail.html' @@ -80,11 +85,20 @@ class NoteDetailView(UpdateView, LoggedInViewWithUser): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['notes_list'] = Note.objects.filter(owner=self.request.user) + context['notebook_form'] = NotebookForm return context + def form_valid(self, form): + self.object = form.save() + tags = serializers.serialize("json", self.object.tags.all()) + data = { + 'tags': tags, + 'notebook': {'name': self.object.notebook.name, 'color': self.object.notebook.color_rgb} + } + return JsonResponse(data, safe=True) + -class NoteCreateView(CreateView, LoggedInViewWithUser): +class NoteCreateView(LoggedInViewWithUser, CreateView): model = Note form_class = NoteForm template_name = 'notes/notes_create.html' @@ -99,27 +113,76 @@ class NoteCreateView(CreateView, LoggedInViewWithUser): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['notes_list'] = Note.objects.filter(owner=self.request.user).select_related() + context['notebook_form'] = NotebookForm + # context['notes_list'] = Note.objects.filter(owner=self.request.user).select_related() + return context + + +class NoteTagView(BaseListView): + model = Note + template_name = 'notes/notes_list.html' + + def get_queryset(self): + ''' + This can generate a crazy amount of joins if there's a lot of tags + have to keep an eye on it. Would be better to do: + from django.db.models import Q + from functools import reduce + from operator import and_, or_ + #query = reduce(and_, (Q(tags__slug=t) for t in self.tag_list)) + # Note.objects.filter(query, owner=self.request.user) + But that doesn't work for some reason. + ''' + if not self.request.user.is_anonymous: + try: + tags = self.kwargs['slug'].split("+") + except ValueError: + tags = self.kwargs['slug'] + self.tag_list = [x.strip() for x in tags] + qs = Note.objects.prefetch_related('tags').filter(owner=self.request.user).select_related('notebook') + for tag in self.tag_list: + qs = qs.filter(tags__slug=tag) + return qs + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + #context['notes_list'] = Note.objects.filter(owner=self.request.user) + context['tags'] = LuxTag.objects.filter(slug__in=self.tag_list) return context -class NotebookListView(CreateView, LoggedInViewWithUser): +################## +# Notebook Views +################## + + +class NotebookListView(LoggedInViewWithUser, CreateView): model = Notebook form_class = NotebookForm - template_name = 'notes/notebook_create.html' + template_name = 'notes/notebook_list.html' def get_queryset(self): if not self.request.user.is_anonymous: return Notebook.objects.filter(owner=self.request.user) + def form_valid(self, form): + form.instance.owner = self.request.user + self.object = form.save() + return super(NotebookListView, self).form_valid(form) + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['notebook_list'] = Notebook.objects.filter(owner=self.request.user) - context['notes_list'] = Note.objects.filter(owner=self.request.user).select_related() + NotebookFormSet = modelformset_factory(Notebook, form=NotebookForm, extra=0) + context['notebook_form_list'] = NotebookFormSet(queryset=Notebook.objects.filter(owner=self.request.user).exclude(name="Trash").select_related().annotate(note_count=Count('note'))) + #context['notebook_list'] = Notebook.objects.filter(owner=self.request.user).exclude(name="Trash").select_related().annotate(note_count=Count('note')) + #context['notes_list'] = Note.objects.filter(owner=self.request.user).select_related() return context + def get_success_url(self): + return reverse_lazy('notebooks:detail', kwargs={'slug': self.object.slug}) + -class NotebookDetailView(DetailView, LoggedInViewWithUser): +class NotebookDetailView(BaseDetailView): model = Notebook def get_object(self): @@ -129,11 +192,16 @@ class NotebookDetailView(DetailView, LoggedInViewWithUser): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context['notes_list'] = Note.objects.filter(owner=self.request.user).select_related() + #context['notes_list'] = Note.objects.filter(owner=self.request.user).select_related() context['form'] = self.form return context +################## +# API Views +################## + + class IsOwnerOrDeny(permissions.BasePermission): """ Custom permission to only allow owners to post to their endpoint diff --git a/apps/utils/views.py b/apps/utils/views.py index 595e102..ec3a902 100644 --- a/apps/utils/views.py +++ b/apps/utils/views.py @@ -1,6 +1,6 @@ from itertools import chain import json -from django.http import Http404, HttpResponse +from django.http import Http404, HttpResponse, JsonResponse from django.apps import apps from django.views.generic import ListView from django.views.generic.base import View, RedirectView @@ -42,6 +42,31 @@ class LoggedInViewWithUser(View): return kwargs + +class AjaxableResponseMixin: + """ + Mixin to add AJAX support to a form. + Must be used with an object-based FormView (e.g. CreateView) + """ + def form_invalid(self, form): + response = super().form_invalid(form) + if self.request.is_ajax(): + return JsonResponse(form.errors, status=400) + else: + return response + + def form_valid(self, form): + # We make sure to call the parent's form_valid() method because + # it might do some processing (in the case of CreateView, it will + # call form.save() for example). + response = super().form_valid(form) + if self.request.is_ajax(): + data = { + 'pk': self.object.pk, + } + return JsonResponse(data) + else: + return response ''' class TagAutocomplete(autocomplete.Select2QuerySetView): def get_queryset(self): diff --git a/apps/utils/widgets.py b/apps/utils/widgets.py index f4a7a4a..2745932 100644 --- a/apps/utils/widgets.py +++ b/apps/utils/widgets.py @@ -2,8 +2,9 @@ import os from django import forms from django.contrib import admin from django.contrib.admin.widgets import AdminFileWidget -from django.contrib.gis.admin import OSMGeoAdmin from django.utils.safestring import mark_safe +from django.forms import widgets +from django.urls import reverse from django.utils.translation import ugettext_lazy as _ from django.template.loader import render_to_string from django.template import Context @@ -130,15 +131,21 @@ class LGEntryFormSmall(forms.ModelForm): } -class OLAdminBase(OSMGeoAdmin): - default_lon = -9285175 - default_lat = 4025046 - default_zoom = 15 - units = True - scrollable = False - map_width = 700 - map_height = 425 - map_template = 'gis/admin/osm.html' - openlayers_url = '/static/admin/js/OpenLayers.js' - - +class RelatedFieldWidgetCanAdd(widgets.Select): + """ + Modifies standard django Select widget to add link after to add new instance + of related model (doesn't check permissions, that's for the form instance) + """ + def __init__(self, related_model, related_url=None, *args, **kw): + super(RelatedFieldWidgetCanAdd, self).__init__(*args, **kw) + if not related_url: + rel_to = related_model + info = (rel_to._meta.app_label, rel_to._meta.object_name.lower()) + related_url = 'admin:%s_%s_add' % info + self.related_url = related_url + + def render(self, name, value, *args, **kwargs): + self.related_url = reverse(self.related_url) + output = [super(RelatedFieldWidgetCanAdd, self).render(name, value, *args, **kwargs)] + output.append('<a class="circle plus small-circle modal-open" href="%s" id="add_id_%s" data-modal-hed-class="%s" data-modal-hed="Add a New %s">New</a>' % (self.related_url, name, name, name.capitalize())) + return mark_safe(u''.join(output)) |