feat(core): numero et article
Parent
e2f5a683b1
révision
b7f893def1
|
@ -1,10 +1,20 @@
|
|||
from wagtail.admin.panels import FieldPanel, TabbedInterface, ObjectList
|
||||
from django import forms
|
||||
from django.db import models
|
||||
|
||||
from wagtail.admin.panels import (
|
||||
FieldPanel,
|
||||
FieldRowPanel,
|
||||
ObjectList,
|
||||
TabbedInterface,
|
||||
)
|
||||
from wagtail.blocks import ListBlock
|
||||
from wagtail.contrib.settings.models import BaseSetting, register_setting
|
||||
from wagtail.core.models import Page
|
||||
from wagtail.fields import RichTextField, StreamField
|
||||
from wagtail.search import index
|
||||
|
||||
from .blocks import BodyBlock, ListLinksBlock, TextLinkBlock
|
||||
from .validators import RangePageValidator
|
||||
|
||||
|
||||
# PAGES
|
||||
|
@ -47,6 +57,116 @@ class StandardPage(AbstractBasePage):
|
|||
FieldPanel('body'),
|
||||
]
|
||||
|
||||
search_fields = Page.search_fields + [
|
||||
index.SearchField('body'),
|
||||
]
|
||||
|
||||
|
||||
def get_futur_num():
|
||||
"""renvoit le prochain numéro"""
|
||||
return Numero.get_last_num() + 1
|
||||
|
||||
|
||||
class Numero(AbstractBasePage):
|
||||
"""Un numero du journal"""
|
||||
|
||||
# Fields
|
||||
|
||||
journal_numero = models.PositiveSmallIntegerField(
|
||||
'Numéro', null=True, blank=True, unique=True, default=get_futur_num
|
||||
)
|
||||
publish_date = models.DateField(
|
||||
'Date de parution',
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Date à laquelle le journal est paru",
|
||||
)
|
||||
image = models.ForeignKey(
|
||||
'wagtailimages.Image',
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name='+',
|
||||
help_text="Image de la une du journal",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_last_num(cls):
|
||||
"""retrouve le dernier numero (retourne un integer)"""
|
||||
numeros = [
|
||||
num.journal_numero
|
||||
for num in cls.objects.all()
|
||||
if isinstance(num.journal_numero, int)
|
||||
] + [0]
|
||||
return max(numeros)
|
||||
|
||||
# wagtail attributes
|
||||
|
||||
subpage_types = ['Article']
|
||||
|
||||
content_panels = Page.content_panels + [
|
||||
FieldPanel('journal_numero'),
|
||||
FieldPanel('publish_date'),
|
||||
FieldPanel('image'),
|
||||
]
|
||||
|
||||
|
||||
class Article(StandardPage):
|
||||
"""Un article du journal"""
|
||||
|
||||
chapeau = RichTextField(blank=True)
|
||||
page_number = models.CharField(
|
||||
'numero(s) de page(s)',
|
||||
max_length=16,
|
||||
validators=[RangePageValidator],
|
||||
help_text=(
|
||||
"Le numéro de page au sein du journal papier. Si l'article s'étend"
|
||||
" sur plusieurs pages, vous devez les séparer avec un tiret"
|
||||
),
|
||||
)
|
||||
image = models.ForeignKey(
|
||||
'wagtailimages.Image',
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name='+',
|
||||
help_text="Illustration de l'article",
|
||||
)
|
||||
breve = models.BooleanField(
|
||||
'brève',
|
||||
default=False,
|
||||
help_text="Détermine si l'article est une brève ou non",
|
||||
)
|
||||
|
||||
parent_page_types = ['Numero']
|
||||
|
||||
content_panels = [
|
||||
FieldPanel('title'),
|
||||
FieldPanel('chapeau'),
|
||||
FieldRowPanel(
|
||||
[
|
||||
FieldPanel(
|
||||
'page_number',
|
||||
widget=forms.TextInput(
|
||||
attrs={'placeholder': "exemple: 1 ou 2-5"}
|
||||
),
|
||||
),
|
||||
FieldPanel('breve'),
|
||||
],
|
||||
heading="Métadonnées",
|
||||
),
|
||||
FieldPanel('image'),
|
||||
FieldPanel('body'),
|
||||
]
|
||||
|
||||
search_fields = Page.search_fields + [
|
||||
index.SearchField('body'),
|
||||
]
|
||||
|
||||
def is_void(self):
|
||||
"""return True if has no editorial content"""
|
||||
return not self.chapeau and not self.body
|
||||
|
||||
|
||||
# SITE SETTINGS
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
from wagtail.core.models import Site
|
||||
|
||||
import pytest
|
||||
|
||||
from ..models import HomePage
|
||||
from .factories import ArticleFactory, HomePageFactory, NumeroFactory
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def home_page():
|
||||
site = Site.objects.get(is_default_site=True)
|
||||
if not isinstance(site.root_page, HomePage):
|
||||
site.root_page = HomePageFactory()
|
||||
site.save()
|
||||
return site.root_page
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def numeros(home_page):
|
||||
return NumeroFactory.create_batch(2, parent=home_page)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def articles(numeros):
|
||||
return ArticleFactory.create_batch(3, parent=numeros[0])
|
|
@ -0,0 +1,28 @@
|
|||
import factory
|
||||
from wagtail_factories import PageFactory
|
||||
|
||||
from ..models import Article, HomePage, Numero
|
||||
|
||||
|
||||
class HomePageFactory(PageFactory):
|
||||
title = "Accueil"
|
||||
|
||||
class Meta:
|
||||
model = HomePage
|
||||
|
||||
|
||||
class NumeroFactory(PageFactory):
|
||||
journal_numero = factory.Sequence(int)
|
||||
title = factory.LazyAttribute(lambda o: f'Le numéro {o.journal_numero}')
|
||||
|
||||
class Meta:
|
||||
model = Numero
|
||||
|
||||
|
||||
class ArticleFactory(PageFactory):
|
||||
title = factory.Faker('sentence')
|
||||
chapeau = factory.Faker('sentence')
|
||||
page_number = factory.Iterator(['1', '2', '3-5'])
|
||||
|
||||
class Meta:
|
||||
model = Article
|
|
@ -0,0 +1,23 @@
|
|||
import pytest
|
||||
|
||||
from labrique.core.models import Numero
|
||||
|
||||
from .factories import ArticleFactory
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestNumero:
|
||||
def test_get_last_num_no_num(self):
|
||||
assert Numero.get_last_num() == 0
|
||||
|
||||
def test_get_last_num(self, numeros):
|
||||
assert Numero.get_last_num() == 2
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
class TestArticle:
|
||||
def test_is_void(self):
|
||||
assert ArticleFactory.create().is_void() is False
|
||||
|
||||
def test_is_not_void(self):
|
||||
assert ArticleFactory.create(chapeau='', body='').is_void() is True
|
|
@ -0,0 +1,32 @@
|
|||
from django.core.exceptions import ValidationError
|
||||
|
||||
import pytest
|
||||
|
||||
from labrique.core.validators import RangePageValidator
|
||||
|
||||
|
||||
class TestRagePageValidator:
|
||||
validate_page = RangePageValidator()
|
||||
|
||||
@pytest.mark.parametrize('page_number', ['1', '42', '1984'])
|
||||
def test_one_page(self, page_number):
|
||||
assert self.validate_page(page_number) is None
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'page_range', ['1-2', '42-43', '1-99999999999999']
|
||||
)
|
||||
def test_valid_range_page(self, page_range):
|
||||
assert self.validate_page(page_range) is None
|
||||
|
||||
@pytest.mark.parametrize('page_range', ['-', '-2', '2-', '-1-1', 'spam'])
|
||||
def test_invalid_range_page(self, page_range):
|
||||
with pytest.raises(ValidationError):
|
||||
self.validate_page(page_range)
|
||||
|
||||
@pytest.mark.parametrize('page_range', ['42-1', '1-0'])
|
||||
def test_range_page_not_increasing(self, page_range):
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
self.validate_page(page_range)
|
||||
assert exc_info.value.args[0] == (
|
||||
'La première page doit être inférieure à la dernière'
|
||||
)
|
|
@ -0,0 +1,17 @@
|
|||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import RegexValidator
|
||||
from django.utils.deconstruct import deconstructible
|
||||
|
||||
|
||||
@deconstructible
|
||||
class RangePageValidator(RegexValidator):
|
||||
regex = r'^[0-9]*$|^[0-9]+\-[0-9]+$'
|
||||
|
||||
def __call__(self, value):
|
||||
super().__call__(value)
|
||||
if '-' in value:
|
||||
first, last = value.split('-')
|
||||
if not int(first) < int(last):
|
||||
raise ValidationError(
|
||||
"La première page doit être inférieure à la dernière"
|
||||
)
|
|
@ -4,6 +4,7 @@
|
|||
# ------------------------------------------------------------------------------
|
||||
pytest
|
||||
pytest-django
|
||||
wagtail-factories
|
||||
|
||||
# Code quality
|
||||
# ------------------------------------------------------------------------------
|
||||
|
|
Chargement…
Référencer dans un nouveau ticket