add skeleton model and management pages for agendas

This commit is contained in:
Frédéric Péters 2016-02-13 11:57:57 +01:00
parent d13dbf8178
commit 101c462157
14 changed files with 256 additions and 5 deletions

View File

@ -4,7 +4,7 @@ recursive-include chrono/locale *.po *.mo
# static
# templates
recursive-include combo/manager/templates *.html
recursive-include chrono/manager/templates *.html
include COPYING README
include MANIFEST.in

View File

View File

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
]
operations = [
migrations.CreateModel(
name='Agenda',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('label', models.CharField(max_length=50, verbose_name='Label')),
('slug', models.SlugField(verbose_name='Slug')),
],
options={
'ordering': ['label'],
},
bases=(models.Model,),
),
]

View File

46
chrono/agendas/models.py Normal file
View File

@ -0,0 +1,46 @@
# chrono - agendas system
# Copyright (C) 2016 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from django.core.urlresolvers import reverse
from django.db import models
from django.utils.text import slugify
from django.utils.translation import ugettext_lazy as _
class Agenda(models.Model):
label = models.CharField(_('Label'), max_length=50)
slug = models.SlugField(_('Slug'))
class Meta:
ordering = ['label']
def save(self, *args, **kwargs):
if not self.slug:
base_slug = slugify(self.label)
slug = base_slug
i = 1
while True:
try:
Agenda.objects.get(slug=slug)
except self.DoesNotExist:
break
slug = '%s-%s' % (base_slug, i)
i += 1
self.slug = slug
super(Agenda, self).save(*args, **kwargs)
def get_absolute_url(self):
return reverse('chrono-manager-agenda-view', kwargs={'pk': self.id})

View File

@ -0,0 +1,22 @@
{% extends "chrono/manager_home.html" %}
{% load i18n %}
{% block appbar %}
{% if object.id %}
<h2>{% trans "Edit Agenda" %}</h2>
{% else %}
<h2>{% trans "New Agenda" %}</h2>
{% endif %}
{% endblock %}
{% block content %}
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<div class="buttons">
<button>{% trans "Save" %}</button>
<a class="cancel" href="{% url 'chrono-manager-homepage' %}">{% trans 'Cancel' %}</a>
</div>
</form>
{% endblock %}

View File

@ -0,0 +1,18 @@
{% extends "chrono/manager_home.html" %}
{% load i18n %}
{% block appbar %}
<h2>{% trans 'Agenda' %} - {{ object.label }}</h2>
<a rel="popup" href="{% url 'chrono-manager-agenda-delete' pk=object.id %}">{% trans 'Delete' %}</a>
<a rel="popup" href="{% url 'chrono-manager-agenda-edit' pk=object.id %}">{% trans 'Rename' %}</a>
{% endblock %}
{% block breadcrumb %}
{{ block.super }}
{% if object %}
<a href="{% url 'chrono-manager-agenda-view' pk=object.id %}">{{object.label}}</a>
{% endif %}
{% endblock %}
{% block content %}
{% endblock %}

View File

@ -0,0 +1,17 @@
{% extends "chrono/manager_home.html" %}
{% load i18n %}
{% block appbar %}
<h2>{{ view.model.get_verbose_name }}</h2>
{% endblock %}
{% block content %}
<form method="post">
{% csrf_token %}
{% blocktrans %}Are you sure you want to delete this?{% endblocktrans %}
<div class="buttons">
<button>{% trans 'Confirm Deletion' %}</button>
<a class="cancel" href="{{ object.get_absolute_url }}">{% trans 'Cancel' %}</a>
</div>
</form>
{% endblock %}

View File

@ -3,12 +3,18 @@
{% block appbar %}
<h2>{% trans 'Agendas' %}</h2>
<a rel="popup" href="{% url 'chrono-manager-agenda-add' %}">{% trans 'New' %}</a>
{% endblock %}
{% block content %}
{% if object_list %}
<div class="objects-list">
<div>
<ul class="objects-list single-links">
{% for object in object_list %}
<li><a href="{% url 'chrono-manager-agenda-view' pk=object.id %}">{{ object.label }}</a></li>
{% endfor %}
</ul>
</div>
{% else %}
<div class="big-msg-info">

View File

@ -20,5 +20,13 @@ from . import views
urlpatterns = patterns('chrono.views',
url(r'^$', views.homepage, name='chrono-manager-homepage'),
url(r'^agendas/add/$', views.agenda_add,
name='chrono-manager-agenda-add'),
url(r'^agendas/(?P<pk>\w+)/$', views.agenda_view,
name='chrono-manager-agenda-view'),
url(r'^agendas/(?P<pk>\w+)/edit$', views.agenda_edit,
name='chrono-manager-agenda-edit'),
url(r'^agendas/(?P<pk>\w+)/delete$', views.agenda_delete,
name='chrono-manager-agenda-delete'),
url(r'^menu.json$', views.menu_json),
)

View File

@ -16,19 +16,54 @@
import json
from django.core.urlresolvers import reverse
from django.core.urlresolvers import reverse_lazy, reverse
from django.http import HttpResponse
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import force_text
from django.views.generic import TemplateView
from django.views.generic import (DetailView, CreateView, UpdateView,
ListView, DeleteView)
from chrono.agendas.models import Agenda
class HomepageView(TemplateView):
class HomepageView(ListView):
template_name = 'chrono/manager_home.html'
model = Agenda
homepage = HomepageView.as_view()
class AgendaAddView(CreateView):
template_name = 'chrono/manager_agenda_form.html'
model = Agenda
fields = ['label']
agenda_add = AgendaAddView.as_view()
class AgendaEditView(UpdateView):
template_name = 'chrono/manager_agenda_form.html'
model = Agenda
fields = ['label']
agenda_edit = AgendaEditView.as_view()
class AgendaDeleteView(DeleteView):
template_name = 'chrono/manager_confirm_delete.html'
model = Agenda
success_url = reverse_lazy('chrono-manager-homepage')
agenda_delete = AgendaDeleteView.as_view()
class AgendaView(DetailView):
template_name = 'chrono/manager_agenda_view.html'
model = Agenda
agenda_view = AgendaView.as_view()
def menu_json(request):
response = HttpResponse(content_type='application/json')
label = _('Agendas')

View File

@ -53,6 +53,7 @@ INSTALLED_APPS = (
'django.contrib.messages',
'django.contrib.staticfiles',
'gadjo',
'chrono.agendas',
'chrono.manager',
)

27
tests/test_agendas.py Normal file
View File

@ -0,0 +1,27 @@
import pytest
from chrono.agendas.models import Agenda
pytestmark = pytest.mark.django_db
def test_slug():
agenda = Agenda(label=u'Foo bar')
agenda.save()
assert agenda.slug == 'foo-bar'
def test_existing_slug():
agenda = Agenda(label=u'Foo bar', slug='bar')
agenda.save()
assert agenda.slug == 'bar'
def test_duplicate_slugs():
agenda = Agenda(label=u'Foo baz')
agenda.save()
assert agenda.slug == 'foo-baz'
agenda = Agenda(label=u'Foo baz')
agenda.save()
assert agenda.slug == 'foo-baz-1'
agenda = Agenda(label=u'Foo baz')
agenda.save()
assert agenda.slug == 'foo-baz-2'

View File

@ -4,6 +4,8 @@ from webtest import TestApp
from chrono.wsgi import application
from chrono.agendas.models import Agenda
pytestmark = pytest.mark.django_db
@pytest.fixture
@ -38,3 +40,47 @@ def test_logout(admin_user):
app = login(TestApp(application))
app.get('/logout/')
assert app.get('/manage/', status=302).location == 'http://localhost:80/login/?next=/manage/'
def test_menu_json(admin_user):
app = login(TestApp(application))
resp = app.get('/manage/menu.json', status=200)
assert resp.json[0]['url'] == 'http://localhost:80/manage/'
assert resp.json[0]['label'] == 'Agendas'
resp2 = app.get('/manage/menu.json?callback=Q', status=200)
assert resp2.body == 'Q(%s);' % resp.body
def test_add_agenda(admin_user):
app = login(TestApp(application))
resp = app.get('/manage/', status=200)
resp = resp.click('New')
resp.form['label'] = 'Foo bar'
resp = resp.form.submit()
assert resp.location == 'http://localhost:80/manage/agendas/1/'
resp = resp.follow()
assert '<h2>Agenda - Foo bar</h2>' in resp.body
def test_rename_agenda(admin_user):
agenda = Agenda(label=u'Foo bar')
agenda.save()
app = login(TestApp(application))
resp = app.get('/manage/', status=200)
resp = resp.click('Foo bar')
resp = resp.click('Rename')
assert resp.form['label'].value == 'Foo bar'
resp.form['label'] = 'Foo baz'
resp = resp.form.submit()
assert resp.location == 'http://localhost:80/manage/agendas/1/'
resp = resp.follow()
assert '<h2>Agenda - Foo baz</h2>' in resp.body
def test_delete_agenda(admin_user):
agenda = Agenda(label=u'Foo bar')
agenda.save()
app = login(TestApp(application))
resp = app.get('/manage/', status=200)
resp = resp.click('Foo bar')
resp = resp.click('Delete')
resp = resp.form.submit()
assert resp.location == 'http://localhost:80/manage/'
resp = resp.follow()
assert not 'Foo bar' in resp.body