Skip to content

Commit

Permalink
Merge pull request #100 from pinax/slug-and-markup
Browse files Browse the repository at this point in the history
Validate all fields when saving Post model
  • Loading branch information
grahamu authored Feb 15, 2017
2 parents 7843d04 + d4b88d0 commit 1de901e
Show file tree
Hide file tree
Showing 11 changed files with 149 additions and 24 deletions.
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ python:
env:
- DJANGO=1.8
- DJANGO=1.9
- DJANGO=master
- DJANGO=1.10
matrix:
exclude:
- python: "3.3"
env: DJANGO=1.9
- python: "3.3"
env: DJANGO=master
env: DJANGO=1.10
install:
- pip install tox coveralls
script:
Expand Down
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ Javyer DerDerian
Alexis Santos
Joe di Stefano
Enrique García Navalón
Graham Ullrich
8 changes: 8 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Change Log

## 6.0.2

* increased max_length of Post.slug field from 50 to 90 chars, matching Post.title field length.

## 6.0.1

* fix templatetag scoping

## 6.0.0

* added support for frontend editing
Expand Down
15 changes: 10 additions & 5 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ for site-level company and project blogs, is now known as `pinax-blog`.
!!! note "Pinax Ecosystem"
This app was developed as part of the Pinax ecosystem but is just a Django app
and can be used independently of other Pinax apps.

To learn more about Pinax, see <http://pinaxproject.com/>


Expand All @@ -16,13 +16,15 @@ Install the development version:

pip install pinax-blog

Add `pinax-blog` to your `INSTALLED_APPS` setting. Also add the `sites` framework
if you don't already use it:
Add `pinax-blog` to your `INSTALLED_APPS` setting.
Add dependency `pinax-images` to your `INSTALLED_APPS` setting as well.
Also add the `sites` framework if you don't already use it:

INSTALLED_APPS = (
# ...
"django.contrib.sites"
"pinax.blog",
"pinax.images",
# ...
)

Expand All @@ -35,16 +37,19 @@ Add entry to your `urls.py`:
urlpatterns = [
# ...
url(r"^blog/", include("pinax.blog.urls", namespace="pinax_blog")),
url(r"^ajax/images/", include("pinax.images.urls", namespace="pinax_images")),
# ...
]


## Dependencies

* `django-appconf>=1.0.1`
* `Pillow>=2.0`
* `Markdown>=2.6`
* `pytz>=2016.6.1`
* `Pillow>=3.0.0`
* `Markdown>=2.6.5`
* `Pygments>=2.0.2`
* `pinax-images>=2.0.0`

Optionally, if you want `creole` support for a mark up choice:

Expand Down
1 change: 1 addition & 0 deletions pinax/blog/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ def save(self, blog=None, author=None):
if self.section:
post.section = self.section
post.slug = slugify(post.title)
post.markup = self.markup_choice
return self.save_post(post)

class Meta:
Expand Down
20 changes: 20 additions & 0 deletions pinax/blog/migrations/0018_auto_20170213_1035.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-02-13 10:35
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('blog', '0017_remove_post_tweet_text'),
]

operations = [
migrations.AlterField(
model_name='post',
name='slug',
field=models.SlugField(max_length=90, verbose_name='Slug'),
),
]
3 changes: 2 additions & 1 deletion pinax/blog/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class Post(models.Model):
section = models.ForeignKey(Section)

title = models.CharField(_("Title"), max_length=90)
slug = models.SlugField(_("Slug"), unique=settings.PINAX_BLOG_SLUG_UNIQUE)
slug = models.SlugField(_("Slug"), max_length=90, unique=settings.PINAX_BLOG_SLUG_UNIQUE)
author = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="posts", verbose_name=_("Author"))

markup = models.CharField(_("Markup"), max_length=25, choices=settings.PINAX_BLOG_MARKUP_CHOICES)
Expand Down Expand Up @@ -162,6 +162,7 @@ def save(self, **kwargs):
self.published = timezone.now()
if not ImageSet.objects.filter(blog_posts=self).exists():
self.image_set = ImageSet.objects.create(created_by=self.author)
self.full_clean()
super(Post, self).save(**kwargs)

@property
Expand Down
47 changes: 47 additions & 0 deletions pinax/blog/tests/test_forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from __future__ import absolute_import

from django.contrib.auth import get_user_model
from django.test import TestCase

from ..forms import PostForm
from ..models import (
Blog,
Post,
Section,
)
from .tests import randomword


class TestForms(TestCase):

def setUp(self):
super(TestForms, self).setUp()

self.user = get_user_model().objects.create_user(
username="patrick",
password="password"
)
self.user.save()
self.blog = Blog.objects.first()
self.section = Section.objects.create(name="hello", slug="hello")
self.content = "You won't believe what happened next!"
self.teaser = "Only his dog knows the truth"
self.title_len = Post._meta.get_field("title").max_length

def test_max_slug(self):
"""
Ensure Post can be created with slug same length as title.
"""
title = randomword(self.title_len)
form_data = {
"section": self.section,
"title": title,
"content": self.content,
"teaser": self.teaser,
"state": 1
}
form = PostForm(data=form_data)
# slug field is not validated in form
self.assertTrue(form.is_valid())
# slug field is set (from title) in model .save() method
self.assertTrue(form.save(blog=self.blog, author=self.user))
64 changes: 53 additions & 11 deletions pinax/blog/tests/tests.py
Original file line number Diff line number Diff line change
@@ -1,43 +1,60 @@
from django.core.urlresolvers import reverse
from django.test import TestCase
from __future__ import absolute_import

import random

from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
from django.test import TestCase
from django.utils.text import slugify

from ..models import Blog, Post, Section


ascii_lowercase = 'abcdefghijklmnopqrstuvwxyz'


def randomword(length):
return ''.join(random.choice(ascii_lowercase) for i in range(length))


class TestBlog(TestCase):

def setUp(self):
"""
Create default Sections and Posts.
"""
blog = Blog.objects.first()
apples = Section.objects.create(name="Apples", slug="apples")
oranges = Section.objects.create(name="Oranges", slug="oranges")
self.blog = Blog.objects.first()
self.apples = Section.objects.create(name="Apples", slug="apples")
self.oranges = Section.objects.create(name="Oranges", slug="oranges")

self.password = "eldarion"
self.user = get_user_model().objects.create_user(
username="patrick",
password=self.password
)
self.user.save()
self.markup = "markdown"

# Create two published Posts, one in each section.
self.orange_title = "Orange You Wonderful"
self.orange_post = Post.objects.create(blog=blog,
section=oranges,
self.orange_slug = slugify(self.orange_title)
self.orange_post = Post.objects.create(blog=self.blog,
section=self.oranges,
title=self.orange_title,
slug=self.orange_title,
slug=self.orange_slug,
author=self.user,
markup=self.markup,
state=Post.STATE_CHOICES[-1][0])

self.apple_title = "Apple of My Eye"
self.apple_post = Post.objects.create(blog=blog,
section=apples,
self.apple_slug = slugify(self.apple_title)
self.apple_post = Post.objects.create(blog=self.blog,
section=self.apples,
title=self.apple_title,
slug=self.apple_title,
slug=self.apple_slug,
author=self.user,
markup=self.markup,
state=Post.STATE_CHOICES[-1][0])


Expand Down Expand Up @@ -85,3 +102,28 @@ def test_all_posts(self):
self.assertEqual(response.context_data["post_list"].count(), 2)
self.assertIn(self.orange_post, response.context_data["post_list"])
self.assertIn(self.apple_post, response.context_data["post_list"])


class TestModelFieldValidation(TestBlog):

def test_overlong_slug(self):
title_len = Post._meta.get_field("title").max_length
title = randomword(title_len)
slug_len = Post._meta.get_field("slug").max_length
slug = randomword(slug_len + 1)
slug_post = Post(blog=self.blog,
section=self.apples,
title=title,
slug=slug,
author=self.user,
state=Post.STATE_CHOICES[-1][0])

with self.assertRaises(ValidationError) as context_manager:
slug_post.save()

the_exception = context_manager.exception
self.assertIn(
"Ensure this value has at most {} characters (it has {})."
.format(slug_len, len(slug)),
the_exception.messages
)
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def read(*parts):

setup(
name=NAME,
version="6.0.1",
version="6.0.2",
description=DESCRIPTION,
long_description=read("README.rst"),
url=URL,
Expand Down
8 changes: 4 additions & 4 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ exclude = migrations/*,docs/*

[tox]
envlist =
py27-{1.8,1.9,master},
py27-{1.8,1.9,1.10},
py33-{1.8},
py34-{1.8,1.9,master},
py35-{1.8,1.9,master}
py34-{1.8,1.9,1.10},
py35-{1.8,1.9,1.10}

[testenv]
deps =
coverage==4.0.2
flake8==2.5.0
1.8: Django>=1.8,<1.9
1.9: Django>=1.9,<1.10
master: https://github.com/django/django/tarball/master
1.10: Django>=1.10,<1.11
usedevelop = True
setenv =
LANG=en_US.UTF-8
Expand Down

0 comments on commit 1de901e

Please sign in to comment.