aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorluxagraf <sng@luxagraf.net>2018-11-19 11:51:46 -0600
committerluxagraf <sng@luxagraf.net>2018-11-19 11:51:46 -0600
commit2ef6414abec4e606d0fd96babc849cc2bde2bb38 (patch)
treedf54a3a0e6d8f1f86a1530394f67087eb97ac34f
parenta0b95dc2dfb84c682bb8f677e5d471f84e5028fe (diff)
Updated notes to default to private and changed body_markdown to body
-rw-r--r--apps/notes/admin.py13
-rw-r--r--apps/notes/migrations/0004_auto_20181117_2039.py27
-rw-r--r--apps/notes/migrations/0005_auto_20181119_1145.py28
-rw-r--r--apps/notes/models.py12
-rw-r--r--apps/notes/serializers.py4
-rw-r--r--apps/notes/tests/test_models.py36
-rw-r--r--apps/notes/tests/test_views.py58
-rw-r--r--apps/notes/urls.py21
-rw-r--r--apps/notes/views.py59
-rw-r--r--design/templates/notes/notes_list.html9
10 files changed, 220 insertions, 47 deletions
diff --git a/apps/notes/admin.py b/apps/notes/admin.py
new file mode 100644
index 0000000..dbac05c
--- /dev/null
+++ b/apps/notes/admin.py
@@ -0,0 +1,13 @@
+from django.contrib import admin
+
+from .models import Note, Folder
+
+
+@admin.register(Note)
+class NoteAdmin(admin.ModelAdmin):
+ pass
+
+
+@admin.register(Folder)
+class FolderAdmin(admin.ModelAdmin):
+ pass
diff --git a/apps/notes/migrations/0004_auto_20181117_2039.py b/apps/notes/migrations/0004_auto_20181117_2039.py
new file mode 100644
index 0000000..6fc6f2d
--- /dev/null
+++ b/apps/notes/migrations/0004_auto_20181117_2039.py
@@ -0,0 +1,27 @@
+# Generated by Django 2.1.2 on 2018-11-18 02:39
+
+import datetime
+from django.db import migrations, models
+from django.utils.timezone import utc
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('notes', '0003_note_folder'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='folder',
+ name='date_created',
+ field=models.DateTimeField(blank=True, default=datetime.datetime(2018, 11, 18, 2, 38, 45, 996162, tzinfo=utc), editable=False),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name='folder',
+ name='date_updated',
+ field=models.DateTimeField(blank=True, default=datetime.datetime(2018, 11, 18, 2, 39, 0, 850658, tzinfo=utc), editable=False),
+ preserve_default=False,
+ ),
+ ]
diff --git a/apps/notes/migrations/0005_auto_20181119_1145.py b/apps/notes/migrations/0005_auto_20181119_1145.py
new file mode 100644
index 0000000..9b6cee7
--- /dev/null
+++ b/apps/notes/migrations/0005_auto_20181119_1145.py
@@ -0,0 +1,28 @@
+# Generated by Django 2.1.2 on 2018-11-19 17:45
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('notes', '0004_auto_20181117_2039'),
+ ]
+
+ operations = [
+ migrations.RenameField(
+ model_name='note',
+ old_name='body_markdown',
+ new_name='body',
+ ),
+ migrations.AddField(
+ model_name='note',
+ name='is_public',
+ field=models.BooleanField(default=False),
+ ),
+ migrations.AddField(
+ model_name='note',
+ name='rendered_body',
+ field=models.TextField(null=True),
+ ),
+ ]
diff --git a/apps/notes/models.py b/apps/notes/models.py
index aeb3bb3..318b079 100644
--- a/apps/notes/models.py
+++ b/apps/notes/models.py
@@ -1,6 +1,7 @@
from django.db import models
from django.utils import timezone
from django.template.defaultfilters import slugify
+from django.urls import reverse
from taggit.managers import TaggableManager
@@ -10,6 +11,8 @@ from django.conf import settings
class Folder(models.Model):
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
name = models.CharField(max_length=250)
+ date_created = models.DateTimeField(blank=True, editable=False)
+ date_updated = models.DateTimeField(blank=True, editable=False)
def __str__(self):
return self.name
@@ -20,19 +23,26 @@ class Note(models.Model):
date_created = models.DateTimeField(blank=True, editable=False)
date_updated = models.DateTimeField(blank=True, editable=False)
title = models.CharField(max_length=250)
- body_markdown = models.TextField(null=True)
+ body = models.TextField(null=True)
+ rendered_body = models.TextField(null=True)
url = models.CharField(max_length=250)
slug = models.SlugField(unique=True, blank=True)
folder = models.ForeignKey(Folder, null=True, on_delete=models.SET_NULL)
tags = TaggableManager(blank=True, help_text='Tags')
+ is_public = models.BooleanField(default=False)
def __str__(self):
return self.title
+ def get_absolute_url(self):
+ return reverse("notes:notes-detail", kwargs={"pk": self.pk})
+
def save(self, **kwargs):
# On save, update timestamps (users updated through admin.py)
if not self.id:
self.date_created = timezone.now()
self.slug = slugify(self.title)[:50]
+ if not self.title:
+ self.title = str(self.date_created)
self.date_updated = timezone.now()
super(Note, self).save()
diff --git a/apps/notes/serializers.py b/apps/notes/serializers.py
index d6c7392..0929e79 100644
--- a/apps/notes/serializers.py
+++ b/apps/notes/serializers.py
@@ -9,10 +9,10 @@ class NoteSerializer(TaggitSerializer, serializers.HyperlinkedModelSerializer):
class Meta:
model = Note
- fields = ('title', 'body_markdown', 'url', 'folder', 'tags', 'date_created')
+ fields = ('title', 'body', 'url', 'tags')
class FolderSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Folder
- fields = ('name')
+ fields = ('name',)
diff --git a/apps/notes/tests/test_models.py b/apps/notes/tests/test_models.py
index e6cdb6a..ddb731e 100644
--- a/apps/notes/tests/test_models.py
+++ b/apps/notes/tests/test_models.py
@@ -14,17 +14,31 @@ class FolderModelTest(TestCase):
class NoteModelTest(TestCase):
-
- def test_string_representation(self):
- user = mixer.blend(User, username='test')
- note = Note(
- created_by=user,
+ def setUp(self):
+ self.user = mixer.blend(User, username='test')
+ self.note = Note.objects.create(
+ created_by=self.user,
title="test note",
- body_markdown="the body of the note",
+ body="the body of the note",
+ url="https://luxagraf.net/",
+ tags="mine,cool site"
+ )
+ self.note.save()
+ self.note_no_title = Note.objects.create(
+ created_by=self.user,
+ body="the body of the note",
url="https://luxagraf.net/",
tags="mine,cool site"
- )
- self.assertEqual(str(note), "test note")
- self.assertEqual(str(note.body_markdown), "the body of the note")
- self.assertEqual(str(note.url), "https://luxagraf.net/")
- self.assertEqual(str(note.tags), "mine,cool site")
+ )
+ self.note_no_title.save()
+
+ def test_string_representation(self):
+ self.assertEqual(str(self.note), "test note")
+ self.assertEqual(str(self.note.body), "the body of the note")
+ self.assertEqual(str(self.note.url), "https://luxagraf.net/")
+ self.assertEqual(str(self.note.tags), "mine,cool site")
+ # titleless note gets date
+ self.assertEqual(str(self.note_no_title), str(self.note_no_title.date_created))
+
+ def test_get_absolute_url(self):
+ self.assertEqual(str(self.note.get_absolute_url()), "/notes/%s/" % (self.note.id))
diff --git a/apps/notes/tests/test_views.py b/apps/notes/tests/test_views.py
index 3f21b0b..05cb8db 100644
--- a/apps/notes/tests/test_views.py
+++ b/apps/notes/tests/test_views.py
@@ -1,9 +1,11 @@
import json
from django.test import Client
from django.test import RequestFactory, TestCase
+from django.urls import reverse
from rest_framework.test import force_authenticate
from rest_framework.test import APIRequestFactory
+from rest_framework.test import APIClient
from mixer.backend.django import mixer
from accounts.models import User
@@ -11,28 +13,32 @@ from notes.models import Note
from notes.views import NoteListView, NoteViewSet
-class StoriesViewTest(TestCase):
+class NotesViewsTest(TestCase):
def setUp(self):
# Every test needs access to the request factory.
self.factory = RequestFactory()
# test API with rest framework request factory.
self.apifactory = APIRequestFactory()
- self.user = mixer.blend(User, username='tpynchon', password="gravity")
+ self.client = Client()
+ # and test with rest client
+ self.apiclient = APIClient()
+ self.user = User.objects.create(username='testuser', password='password')
+ self.bad_user = User.objects.create(username='someoneelse', password='password')
self.note = Note.objects.create(
created_by=self.user,
title="test note",
- body_markdown="the body of the note",
+ body="the body of the note",
url="https://luxagraf.net/",
)
self.note.tags.add("mine,cool site")
self.note.save()
def test_list_view(self):
- request = self.factory.get('/%s/notes/' % (self.user.username))
+ request = self.factory.get('/notes/')
request.user = self.user
response = NoteListView.as_view()(request)
self.assertEqual(response.status_code, 200)
- response.render()
+ # bad_user
def test_api_list(self):
# Make an authenticated request to the view...
@@ -45,13 +51,37 @@ class StoriesViewTest(TestCase):
self.assertEqual(api_data['title'], 'test note')
self.assertEqual(api_data['tags'], ['mine,cool site'])
- def test_api_(self):
- # Make an authenticated request to the view...
- request = self.factory.get('/api/notes/')
- force_authenticate(request, user=self.user)
- response = NoteViewSet.as_view({'get': 'list'})(request)
- self.assertEqual(response.status_code, 200)
+ def test_note_create(self):
+ '''
+ post some data to create a new note
+ '''
+ data = {
+ 'title': "test note post",
+ 'body': "the body of the note",
+ 'url': "https://luxagraf.net/",
+ 'tags': [],
+ }
+ self.apiclient.force_authenticate(self.user)
+ url = reverse("notes:notes-list")
+ response = self.apiclient.post(url, data, format='json')
+ self.assertEqual(response.status_code, 201)
+ self.assertEqual(Note.objects.count(), 2)
response.render()
- api_data = json.loads(response.content.decode('utf8'))[0]
- self.assertEqual(api_data['title'], 'test note')
- self.assertEqual(api_data['tags'], ['mine,cool site'])
+ api_data = json.loads(response.content.decode('utf8'))
+ self.assertEqual(api_data['title'], 'test note post')
+ self.assertEqual(api_data['body'], 'the body of the note')
+ self.assertEqual(api_data['tags'], [])
+
+ def test_note_create_bad(self):
+ # create another user
+ data = {
+ 'title': "",
+ 'body': "the body of the note",
+ 'url': "https://luxagraf.net/",
+ 'tags': [],
+ }
+ url = reverse("notes:notes-list")
+ self.apiclient.force_authenticate(self.user)
+ response = self.apiclient.post(url, data, format='json')
+ self.assertEqual(response.status_code, 400)
+ self.assertEqual(Note.objects.count(), 1)
diff --git a/apps/notes/urls.py b/apps/notes/urls.py
index 76bdeb1..cbb1884 100644
--- a/apps/notes/urls.py
+++ b/apps/notes/urls.py
@@ -1,18 +1,17 @@
from django.urls import path
+from django.conf.urls import include
-from . import views
+from rest_framework import routers
+
+from .views import NoteViewSet, FolderViewSet, NoteListView
+
+router = routers.DefaultRouter()
+router.register(r'notes/folder', FolderViewSet, basename="folder")
+router.register(r'notes', NoteViewSet, basename="notes")
app_name = "notes"
urlpatterns = [
- path(
- r'create/',
- views.NoteCreateView.as_view(),
- name="note-create"
- ),
- path(
- r'',
- views.NoteListView.as_view(),
- name="note"
- ),
+ path(r'', NoteListView.as_view(), name='homepage',),
+ path(r'', include(router.urls)),
]
diff --git a/apps/notes/views.py b/apps/notes/views.py
index ddb72ed..e4b8fda 100644
--- a/apps/notes/views.py
+++ b/apps/notes/views.py
@@ -2,11 +2,16 @@ from django.views.generic import CreateView, ListView, UpdateView, DeleteView
from django.views.generic.detail import DetailView
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
+from django.urls import reverse
from rest_framework import viewsets
+from rest_framework.response import Response
+from rest_framework.decorators import list_route
+from rest_framework import permissions
+
from .serializers import NoteSerializer, FolderSerializer
-from .models import Note
-from .forms import NoteForm
+from .models import Note, Folder
@method_decorator(login_required, name='dispatch')
@@ -22,20 +27,58 @@ class NoteListView(ListView):
model = Note
def get_queryset(self):
- return Note.objects.filter(created_by=self.request.user)
+ if not self.request.user.is_anonymous:
+ return Note.objects.filter(created_by=self.request.user)
+ def get_template_names(self):
+ if not self.request.user.is_anonymous:
+ return ['notes/notes_list.html']
+ else:
+ return ['sell.html']
-class NoteCreateView(LoggedInCreateViewWithUser):
- model = Note
- form_class = NoteForm
- template_name = "notes/create.html"
+
+class IsOwnerOrDeny(permissions.BasePermission):
+ """
+ Custom permission to only allow owners to post to their endpoint
+ """
+
+ def has_object_permission(self, request, view, obj):
+ # Write permissions are only allowed to the owner of the snippet.
+ return obj.owner == request.user
class NoteViewSet(viewsets.ModelViewSet):
"""
- API endpoint that allows users to be viewed or edited.
+ API endpoint that allows notes to be viewed or edited.
"""
serializer_class = NoteSerializer
+ permission_classes = (permissions.IsAuthenticated, IsOwnerOrDeny,)
def get_queryset(self):
return Note.objects.filter(created_by=self.request.user).order_by('-date_created')
+
+ @list_route(methods=['post'])
+ def perform_create(self, serializer):
+ serializer.save(created_by=self.request.user)
+ return super(NoteViewSet, self).perform_create(serializer)
+
+ def get_object(self):
+ obj = get_object_or_404(self.get_queryset(), pk=self.kwargs["pk"])
+ if obj.is_public:
+ return obj
+ else:
+ self.check_object_permissions(self.request, obj)
+ return obj
+
+
+class FolderViewSet(viewsets.ModelViewSet):
+ """
+ API endpoint that allows folder to be viewed or edited.
+ """
+ serializer_class = FolderSerializer
+
+ def get_queryset(self):
+ return Folder.objects.filter(created_by=self.request.user).order_by('-date_created')
+
+ def perform_create(self, serializer):
+ serializer.save(created_by=self.request.user)
diff --git a/design/templates/notes/notes_list.html b/design/templates/notes/notes_list.html
new file mode 100644
index 0000000..4451588
--- /dev/null
+++ b/design/templates/notes/notes_list.html
@@ -0,0 +1,9 @@
+{% extends 'base.html' %}
+{% block content %}
+<main>
+ <h1> Notes</h1>
+ <ul>{% for obj in object_list %}
+ <li><a href="{{obj.get_absolute_url}}">{{obj}}</a></li>
+ {% endfor %}</ul>
+</main>
+{% endblock %}