Skip to content
Snippets Groups Projects
Commit 252e8ed8 authored by florimondmanca's avatar florimondmanca
Browse files

add testimony model and API, update tests, update requirements

parent dad5f0a7
No related branches found
No related tags found
No related merge requests found
...@@ -27,5 +27,6 @@ router.register(r'tutoring/sessions', tutoring_views.TutoringSessionViewSet, ...@@ -27,5 +27,6 @@ router.register(r'tutoring/sessions', tutoring_views.TutoringSessionViewSet,
# Showcase site views # Showcase site views
router.register(r'articles', showcase_site_views.ArticleViewSet) router.register(r'articles', showcase_site_views.ArticleViewSet)
router.register(r'categories', showcase_site_views.CategoryViewSet) router.register(r'categories', showcase_site_views.CategoryViewSet)
router.register(r'testimonies', showcase_site_views.TestimonyViewSet)
urlpatterns = router.urls urlpatterns = router.urls
...@@ -18,6 +18,7 @@ from django.conf import settings ...@@ -18,6 +18,7 @@ from django.conf import settings
from django.conf.urls import url from django.conf.urls import url
from django.conf.urls.static import static from django.conf.urls.static import static
from django.contrib import admin from django.contrib import admin
from django.views.generic import RedirectView
from rest_framework.documentation import include_docs_urls from rest_framework.documentation import include_docs_urls
from rest_framework.authtoken.views import obtain_auth_token from rest_framework.authtoken.views import obtain_auth_token
...@@ -25,10 +26,12 @@ from rest_framework.authtoken.views import obtain_auth_token ...@@ -25,10 +26,12 @@ from rest_framework.authtoken.views import obtain_auth_token
urlpatterns = [ urlpatterns = [
url(r'^admin/', admin.site.urls), url(r'^admin/', admin.site.urls),
url(r'^api/', include('api.urls')), url(r'^api/', include('api.urls')),
url(r'^api/docs/', include_docs_urls(title='OSER_CS API')), url(r'^api/docs/', include_docs_urls(title='OSER_CS API', public=False)),
url('api/auth/', include('rest_framework.urls', url(r'^api/auth/', include('rest_framework.urls',
namespace='rest_framework')), namespace='rest_framework')),
url(r'^api/auth/get-token/$', obtain_auth_token, name='get-auth-token'), url(r'^api/auth/get-token/$', obtain_auth_token, name='get-auth-token'),
url(r'^$', RedirectView.as_view(url='api/docs/', permanent=True),
name='index'),
] ]
# Serve static and media files with Django during development # Serve static and media files with Django during development
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
from django.contrib import admin from django.contrib import admin
from .models import Article from .models import Article
from .models import Category from .models import Category
from .models import Testimony
# Register your models here. # Register your models here.
...@@ -31,3 +32,17 @@ class CategoryAdmin(admin.ModelAdmin): ...@@ -31,3 +32,17 @@ class CategoryAdmin(admin.ModelAdmin):
class Meta: # noqa class Meta: # noqa
model = Category model = Category
@admin.register(Testimony)
class TestimonyAdmin(admin.ModelAdmin):
"""Testimony admin panel."""
list_display = ('__str__', 'get_preview', 'created',)
list_filter = ('created',)
def get_preview(self, obj):
if len(obj.content) > 100:
return obj.content[:100] + ' […]'
return obj.content
get_preview.short_description = 'Aperçu'
...@@ -45,16 +45,21 @@ class Article(models.Model): ...@@ -45,16 +45,21 @@ class Article(models.Model):
pinned : bool pinned : bool
""" """
title = models.CharField('titre', max_length=300) title = models.CharField('titre', max_length=300,
help_text="Titre de l'article")
slug = models.SlugField(max_length=100, unique=True) slug = models.SlugField(max_length=100, unique=True)
content = models.TextField('contenu') # TODO add Markdown support content = models.TextField(
'contenu',
help_text="Contenu complet de l'article") # TODO add Markdown support
published = models.DateTimeField('date de publication', auto_now_add=True) published = models.DateTimeField('date de publication', auto_now_add=True)
image = models.ImageField('illustration', blank=True, null=True) image = models.ImageField('illustration', blank=True, null=True)
pinned = models.BooleanField('épinglé', default=False, blank=True) pinned = models.BooleanField('épinglé', default=False, blank=True)
# ^blank=True to allow True of False value (otherwise # ^blank=True to allow True of False value (otherwise
# validation would force pinned to be True) # validation would force pinned to be True)
# see: https://docs.djangoproject.com/fr/2.0/ref/forms/fields/#booleanfield # see: https://docs.djangoproject.com/fr/2.0/ref/forms/fields/#booleanfield
categories = models.ManyToManyField('Category') categories = models.ManyToManyField(
'Category', blank=True,
help_text="Catégories auxquelles rattacher l'article")
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
...@@ -89,3 +94,28 @@ class Article(models.Model): ...@@ -89,3 +94,28 @@ class Article(models.Model):
def __str__(self): def __str__(self):
return str(self.title) return str(self.title)
class Testimony(models.Model):
content = models.TextField('contenu')
author_name = models.CharField('auteur', max_length=300,
help_text="Nom de l'auteur")
author_position = models.CharField(
'position',
max_length=300,
help_text="Position de l'auteur (lycéen, professeur…)")
created = models.DateField('date de création', auto_now_add=True)
@property
def author(self):
return ('{}, {}'
.format(self.author_name, self.author_position.lower()))
author.fget.short_description = 'auteur'
class Meta: # noqa
verbose_name = 'témoignage'
ordering = ('-created', 'author_name',)
def __str__(self):
return self.author
"""Showcase site API serializers.""" """Showcase site API serializers."""
from rest_framework import serializers from rest_framework import serializers
from .models import Article, Category from .models import Article, Category, Testimony
class CategoryField(serializers.RelatedField): class CategoryField(serializers.RelatedField):
...@@ -43,7 +43,7 @@ class ArticleSerializer(serializers.HyperlinkedModelSerializer): ...@@ -43,7 +43,7 @@ class ArticleSerializer(serializers.HyperlinkedModelSerializer):
Suited for: list, retrieve, update, partial_update, delete Suited for: list, retrieve, update, partial_update, delete
""" """
categories = CategoryField(many=True) categories = CategoryField(many=True, required=False)
class Meta: # noqa class Meta: # noqa
model = Article model = Article
...@@ -54,3 +54,11 @@ class ArticleSerializer(serializers.HyperlinkedModelSerializer): ...@@ -54,3 +54,11 @@ class ArticleSerializer(serializers.HyperlinkedModelSerializer):
'slug': {'read_only': True}, 'slug': {'read_only': True},
'url': {'view_name': 'api:article-detail'}, 'url': {'view_name': 'api:article-detail'},
} }
class TestimonySerializer(serializers.ModelSerializer):
"""Serializer for Testimony."""
class Meta: # noqa
model = Testimony
fields = ('id', 'author', 'created', 'content',)
from django.test import TestCase
# Create your tests here.
...@@ -2,8 +2,12 @@ ...@@ -2,8 +2,12 @@
from rest_framework import viewsets from rest_framework import viewsets
from dry_rest_permissions.generics import DRYPermissions from dry_rest_permissions.generics import DRYPermissions
from .serializers import ArticleSerializer, CategorySerializer from .serializers import ArticleSerializer
from .models import Article, Category from .serializers import CategorySerializer
from .serializers import TestimonySerializer
from .models import Article
from .models import Category
from .models import Testimony
# Create your views here. # Create your views here.
...@@ -13,6 +17,7 @@ class ArticleViewSet(viewsets.ModelViewSet): ...@@ -13,6 +17,7 @@ class ArticleViewSet(viewsets.ModelViewSet):
Actions: list, retrieve, create, update, partial_update, destroy Actions: list, retrieve, create, update, partial_update, destroy
""" """
serializer_class = ArticleSerializer serializer_class = ArticleSerializer
queryset = Article.objects.all() queryset = Article.objects.all()
permission_classes = (DRYPermissions,) permission_classes = (DRYPermissions,)
...@@ -23,5 +28,16 @@ class CategoryViewSet(viewsets.ReadOnlyModelViewSet): ...@@ -23,5 +28,16 @@ class CategoryViewSet(viewsets.ReadOnlyModelViewSet):
Actions: list, retrieve Actions: list, retrieve
""" """
serializer_class = CategorySerializer serializer_class = CategorySerializer
queryset = Category.objects.all() queryset = Category.objects.all()
class TestimonyViewSet(viewsets.ReadOnlyModelViewSet):
"""API endpoint to view testimonies.
Actions: list, retrieve
"""
serializer_class = TestimonySerializer
queryset = Testimony.objects.all()
...@@ -192,3 +192,13 @@ class CategoryFactory(factory.DjangoModelFactory): ...@@ -192,3 +192,13 @@ class CategoryFactory(factory.DjangoModelFactory):
model = showcase_site.models.Category model = showcase_site.models.Category
title = factory.Faker('word') title = factory.Faker('word')
class TestimonyFactory(factory.DjangoModelFactory):
class Meta: # noqa
model = showcase_site.models.Testimony
author_name = factory.Faker('name')
author_position = factory.Faker('job')
content = factory.Faker('text', max_nb_chars=300)
"""Testimony API tests."""
from rest_framework import status
from tests.factory import TestimonyFactory
from tests.utils import HyperlinkedAPITestCase
from showcase_site.serializers import TestimonySerializer
class TestimonyEndpointsTest(HyperlinkedAPITestCase):
"""Test access to the testimonies endpoints."""
factory = TestimonyFactory
serializer_class = TestimonySerializer
def perform_list(self):
url = '/api/testimonies/'
response = self.client.get(url)
return response
def test_list_no_authentication_required(self):
self.assertRequestResponse(
self.perform_list,
user=None,
expected_status_code=status.HTTP_200_OK)
def perform_retrieve(self):
obj = self.factory.create()
url = '/api/testimonies/{obj.pk}/'.format(obj=obj)
response = self.client.get(url)
return response
def test_retrieve_no_authentication_required(self):
self.assertRequestResponse(
self.perform_retrieve,
user=None,
expected_status_code=status.HTTP_200_OK)
"""Testimony model tests."""
from tests.factory import TestimonyFactory
from tests.utils import ModelTestCase
import showcase_site.models
class TestimonyTest(ModelTestCase):
"""Test the Testimony model."""
model = showcase_site.models.Testimony
field_tests = {
'created': {
'verbose_name': 'date de création',
},
'author_name': {
'max_length': 300,
'verbose_name': 'auteur',
},
'author_position': {
'verbose_name': 'position',
},
'author': {
'short_description': 'auteur',
},
'content': {
'verbose_name': 'contenu',
},
}
model_tests = {
'verbose_name': 'témoignage',
'ordering': ('-created', 'author_name'),
}
@classmethod
def setUpTestData(cls):
cls.obj = TestimonyFactory.create()
def test_author_property_is_author_name_comma_position_lower(self):
expected = '{}, {}'.format(self.obj.author_name,
self.obj.author_position.lower())
self.assertEqual(self.obj.author, expected)
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
See FieldTestMeta docstring for available field tests. See FieldTestMeta docstring for available field tests.
""" """
from django.core.exceptions import FieldDoesNotExist
# Toggle to True to print a message when a field test method is called. # Toggle to True to print a message when a field test method is called.
PRINT_FIELD_TEST_CALLS = False PRINT_FIELD_TEST_CALLS = False
...@@ -244,8 +246,18 @@ class FieldTestFactory(BaseTestFactory): ...@@ -244,8 +246,18 @@ class FieldTestFactory(BaseTestFactory):
field=field_name, attr=attr, value=value) field=field_name, attr=attr, value=value)
def get_test_kwargs(self, field_name): def get_test_kwargs(self, field_name):
try:
field = self.model._meta.get_field(field_name) field = self.model._meta.get_field(field_name)
return {'field': field} return {'field': field}
except FieldDoesNotExist:
# look for a property instead
try:
prop = getattr(self.model, field_name).fget
return {'field': prop}
except AttributeError:
raise AttributeError(
"'{}' object has no field or property '{}'"
.format(self.model._meta.object_name, field_name))
@staticmethod @staticmethod
def perform_test(attr, value, field): def perform_test(attr, value, field):
......
...@@ -93,7 +93,7 @@ class ModelTestCase(TestCase, metaclass=ModelTestCaseMeta): ...@@ -93,7 +93,7 @@ class ModelTestCase(TestCase, metaclass=ModelTestCaseMeta):
def setUpClass(cls): def setUpClass(cls):
super().setUpClass() super().setUpClass()
if not cls.model: if not cls.model:
raise TypeError('Model attribute not defined in {}' raise TypeError('model not declared on {}'
.format(cls.__name__)) .format(cls.__name__))
......
Django>=2.0 Django==2.0
djangorestframework>=3.7.3 djangorestframework>=3.7.3
factory-boy>=2.9.2 factory-boy>=2.9.2
coreapi-cli>=1.0 coreapi-cli>=1.0
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment