first commit

This commit is contained in:
Élie Bouttier 2016-06-07 22:59:13 +02:00
commit f6e04013e3
44 changed files with 1041 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/venv/
*.pyc
*.swp
*.sqlite3

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "components"]
path = components
url = git://github.com/toulibre/ponyconf-components

0
accounts/__init__.py Normal file
View File

6
accounts/admin.py Normal file
View File

@ -0,0 +1,6 @@
from django.contrib import admin
from accounts.models import *
admin.site.register(User)

5
accounts/apps.py Normal file
View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class AccountsConfig(AppConfig):
name = 'accounts'

17
accounts/forms.py Normal file
View File

@ -0,0 +1,17 @@
from django.forms.models import modelform_factory
from django.contrib.auth.forms import UserCreationForm
from accounts.models import User
__all__ = ['ProfileForm']
ProfileForm = modelform_factory(User,
fields=['first_name', 'last_name', 'email', 'biography'])
class CustomUserCreationForm(UserCreationForm):
class Meta(UserCreationForm.Meta):
model = User
fields = UserCreationForm.Meta.fields + ('biography',)

View File

@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.6 on 2016-06-02 21:26
from __future__ import unicode_literals
import django.contrib.auth.models
import django.core.validators
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0007_alter_validators_add_error_messages'),
]
operations = [
migrations.CreateModel(
name='User',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=30, unique=True, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.')], verbose_name='username')),
('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=30, verbose_name='last name')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('bio', models.TextField(blank=True)),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
],
options={
'abstract': False,
'verbose_name': 'user',
'verbose_name_plural': 'users',
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
]

View File

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.6 on 2016-06-02 21:28
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='user',
name='bio',
),
migrations.AddField(
model_name='user',
name='biography',
field=models.TextField(blank=True, verbose_name='Biography'),
),
]

View File

11
accounts/models.py Normal file
View File

@ -0,0 +1,11 @@
from django.db import models
from django.contrib.auth.models import AbstractUser
__all__ = [ 'User' ]
class User(AbstractUser):
biography = models.TextField(blank=True, verbose_name='Biography')

View File

@ -0,0 +1,28 @@
{% extends 'base.html' %}
{% load bootstrap3 %}
{% block content %}
<div class="page-header">
<h1>
Login
</h1>
</div>
<div class="row">
<div class="col-md-offset-4 col-md-4">
<div class="well">
<form action="" method="post" role="form">
{% bootstrap_form form %}
{% csrf_token %}
<div class="form-group text-center">
<button type="submit" class="btn btn-primary">Submit</button>
<a href="{% if request.GET.prev %}{{ request.GET.prev }}{% else %}{% url 'home' %}{% endif %}"><button class="btn btn-default">Cancel</button></a>
</div>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,43 @@
{% extends 'base.html' %}
{% load bootstrap3 %}
{% block profiletab %} class="active"{% endblock %}
{% block content %}
<div class="page-header">
<h1>Profile</h1>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3>Update profile</h3>
</div>
<div class="panel-body">
<form method="post" class="col-md-4" role="form">
{% csrf_token %}
{% bootstrap_form profileform %}
<div class="form-group">
<button type="submit" name='update-profile' class="btn btn-primary">Update</button>
</div>
</form>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h3>Update password</h3>
</div>
<div class="panel-body">
<form method="post" class="col-md-4" role="form">
{% csrf_token %}
{% bootstrap_form passwordform %}
<div class="form-group">
<button type="submit" name='update-password' class="btn btn-primary">Update</button>
</div>
</form>
</div>
</div>
{% endblock %}

3
accounts/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

11
accounts/urls.py Normal file
View File

@ -0,0 +1,11 @@
from django.conf.urls import url
from django.contrib.auth import views as auth_views
from accounts import views
urlpatterns = [
url(r'^login/$', auth_views.login, {'template_name': 'accounts/login.html'}, name='login'),
url(r'^logout/$', auth_views.logout, {'next_page': '/'}, name='logout'),
url(r'^profile/$', views.profile, name='profile'),
]

36
accounts/views.py Normal file
View File

@ -0,0 +1,36 @@
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.contrib.auth.forms import PasswordChangeForm
from django.contrib import messages
from accounts.forms import *
@login_required
def profile(request):
profileform = None
passwordform = None
if request.method == 'POST':
if 'update-profile' in request.POST:
profileform = ProfileForm(request.POST, instance=request.user)
if profileform.is_valid():
profileform.save()
messages.success(request, 'Profile updated successfully.')
return redirect('profile')
elif 'update-password' in request.POST:
passwordform = PasswordChangeForm(user=request.user, data=request.POST)
if passwordform.is_valid():
passwordform.save()
messages.success(request, 'Password updated successfully.')
return redirect('profile')
if not profileform:
profileform = ProfileForm(None, instance=request.user)
if not passwordform:
passwordform = PasswordChangeForm(None)
return render(request, 'accounts/profile.html', {
'profileform': profileform,
'passwordform': passwordform,
})

36
doc/requirements.txt Normal file
View File

@ -0,0 +1,36 @@
- enregistrement sur le site (pas d'enregistrement fb/linkedin: on aime
le libre ici)
- permettre de proposer une participation (conférence/atelier/small
talk/etc.). Un speaker rentre un abstract et peur associer un ou des
co-speakers.
- l'équipe organisatrice peut voter pour les conférences (avec un
commentaire visible uniquement de l'équipe) et les accepter/refuser
- pour chaque conférence/conférencier, on permet d'échanger avec le
conférencier (lui vois ça comme un mail, mais côté orga c'est centralisé
sur l'UI web... c'est mieux pour le suivi).
- Notification (par mail) pour les administrateurs des
ajout/modification de conférence et des échanges avec les intervenants.
- Gestion du planning des salles (on affecte une track à une salle, mais
cela permet surtout de signaler les doublons d'intervenants/salle/etc.)
- Gestion du planning des bénévoles (ils s'inscrivent)
- Générer un planning XML pour être ingérée par l'appli Android
- Gestion de différents profils: speaker, bénévole, responsable de
track, kernel
- Génération des badges
- Un speaker et un bénévole peuvent être enregistré par un tiers
(responsable de track ou kernel).
- Gestion des sponsors : niveaux de sponsorings et personnes présentes
pour la génération des badges
- Gestion des différentes thématiques (CRUD)
- Vues à réaliser: listing des propositions, conférence, bénévoles,
speakers (permet d'avoir une vue d'ensemble)
- Champ de note interne sur un speaker (pour noter des préférences par
exemple) et sur une proposition de conférence
- Gestion de la date d'ouverture/fermeture de l'appel à conférence
(différente selon le type de participation - i.e. conférence/atelier/stand).
- On propose par défaut des propositions du type: talk, workshop, booth
- Profil des intervenenants: nom, prénom, mail, date et heure d'arrivée,
date et heure de départ, moyen de transport, type de connectique,
hébergement,
Le code de l'annéed ernière est là : https://github.com/toulibre/cdl-site

10
manage.py Executable file
View File

@ -0,0 +1,10 @@
#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ponyconf.settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)

0
ponyconf/__init__.py Normal file
View File

View File

@ -0,0 +1,8 @@
from django.contrib.sites.shortcuts import get_current_site
def site(request):
return {
'site': get_current_site(request),
}

174
ponyconf/settings.py Normal file
View File

@ -0,0 +1,174 @@
"""
Django settings for ponyconf project.
Generated by 'django-admin startproject' using Django 1.9.6.
For more information on this file, see
https://docs.djangoproject.com/en/1.9/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.9/ref/settings/
"""
import os
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'm2d03t^m)!nsborq5a1#e!#m)wjl&-%tu4ew@fxf1_b_t*@36r'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.sites',
'djangobower',
'bootstrap3',
'ponyconf',
'accounts',
'proposals',
]
MIDDLEWARE_CLASSES = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'ponyconf.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'ponyconf.context_processors.site',
],
},
},
]
WSGI_APPLICATION = 'ponyconf.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.9/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Password validation
# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/1.9/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.9/howto/static-files/
STATIC_URL = '/static/'
STATICFILES_FINDERS = [
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
'djangobower.finders.BowerFinder',
]
BOWER_COMPONENTS_ROOT = os.path.join(BASE_DIR, 'components')
BOWER_INSTALLED_APPS = (
'bootstrap',
'jquery',
)
LOGIN_REDIRECT_URL = 'home'
SITE_ID = 1
AUTH_USER_MODEL = 'accounts.User'
BOOTSTRAP3 = {
# The URL to the jQuery JavaScript file
# If not set, "build-in" CDN is used (maxcdn)
#'jquery_url': '//code.jquery.com/jquery.min.js',
'jquery_url': STATIC_URL + 'jquery/dist/jquery.js',
# The Bootstrap base URL
# If not set, "build-in" CDN is used (maxcdn)
#'base_url': '//netdna.bootstrapcdn.com/bootstrap/3.2.0/',
'base_url': STATIC_URL + 'bootstrap/dist/',
# The complete URL to the Bootstrap CSS file
# (None means derive it from base_url)
'css_url': None,
# The complete URL to the Bootstrap CSS file
# (None means no theme)
'theme_url': None,
# The complete URL to the Bootstrap JavaScript file
# (None means derive it from base_url)
'javascript_url': None,
}

View File

View File

View File

@ -0,0 +1,93 @@
{% load staticfiles %}
{% load bootstrap3 %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<link rel="shortcut icon" type="image/png" href="{% static 'img/favicon.ico' %}"/>
{% comment %}<link rel="icon" href="{% static 'favicon.ico' %}">{% endcomment %}
<!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<title>{% block title %}{{ site.name }}{% endblock %}</title>
{% bootstrap_css %}
<link href="{% static 'css/ponyconf.css' %}" rel="stylesheet">
{% block css %}{% endblock %}
{% block js %}{% endblock %}
</head>
<body>
<!-- Static navbar -->
<div class="navbar navbar-default navbar-static-top" role="navigation">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">{{ site.name }}</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li{% block hometab %}{% endblock %}><a href="{% url 'home' %}">Home</a></li>
<li{% block talktab %}{% endblock %}><a href="{% url 'list-talks' %}">Talks</a></li>
<li{% block topictab %}{% endblock %}><a href="{% url 'list-topics' %}">Topics</a></li>
{% block navbar-left %}{% endblock %}
</ul>
<ul class="nav navbar-nav navbar-right">
{% if request.user.is_authenticated %}
{% if request.user.is_staff %}
<li><a href="{% url 'admin:index' %}"><span class="glyphicon glyphicon-cog"></span>&nbsp;Administration</a></li>
{% endif %}
<li{% block profiletab %}{% endblock %}><a href="{% url 'profile' %}" data-toggle="tooltip" data-placement="bottom" title="Profile"><span class="glyphicon glyphicon-user"></span> {{ request.user.username }}</a></li>
<li><a href="{% url 'logout' %}" data-toggle="tooltip" data-placement="bottom" title="Logout"><span class="glyphicon glyphicon-log-out"></span></a></li>
{% else %}
<li><a href="{% url 'login' %}?next={{ request.path }}"><span class="glyphicon glyphicon-log-in"></span>&nbsp;Login</a></li>
{% endif %}
</ul>
{% block navbar-right %}{% endblock %}
</div><!--/.nav-collapse -->
</div><!--/.container-fluid -->
</div>
<div class="container">
<div class="row">
<div class="col-md-12">
{% bootstrap_messages %}
{% block content %}{% endblock %}
</div> <!-- /col -->
</div> <!-- /row -->
<hr>
<footer>
<p class="text-muted">Powered by <a href="https://github.com/toulibre/ponyconf">PonyConf</a></p>
</footer>
</div> <!-- /container -->
<script src="{% bootstrap_jquery_url %}"></script>
{% bootstrap_javascript %}
<script src="{% static 'js/ponyconf.js' %}"></script>
{% block js_end %}{% endblock %}
</body>
</html>

24
ponyconf/urls.py Normal file
View File

@ -0,0 +1,24 @@
"""ponyconf URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/1.9/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: url(r'^$', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.conf.urls import url, include
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
"""
from django.conf.urls import url, include
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^accounts/', include('accounts.urls')),
url(r'^', include('proposals.urls')),
]

16
ponyconf/wsgi.py Normal file
View File

@ -0,0 +1,16 @@
"""
WSGI config for ponyconf project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ponyconf.settings")
application = get_wsgi_application()

0
proposals/__init__.py Normal file
View File

8
proposals/admin.py Normal file
View File

@ -0,0 +1,8 @@
from django.contrib import admin
from proposals.models import *
admin.site.register(Topic)
admin.site.register(Talk)
admin.site.register(Speach)

5
proposals/apps.py Normal file
View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class ProposalsConfig(AppConfig):
name = 'proposals'

9
proposals/forms.py Normal file
View File

@ -0,0 +1,9 @@
from django.forms.models import modelform_factory
from proposals.models import *
__all__ = ['TalkForm']
TalkForm = modelform_factory(Talk, fields=['title', 'description'])

View File

@ -0,0 +1,69 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.6 on 2016-06-02 21:26
from __future__ import unicode_literals
import autoslug.fields
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('sites', '0002_alter_domain_unique'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Speach',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('order', models.IntegerField(choices=[(1, '1'), (2, '2'), (3, '3'), (4, '4'), (5, '5'), (6, '6'), (7, '7')])),
],
options={
'ordering': ['order'],
},
),
migrations.CreateModel(
name='Talk',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=128, verbose_name='Title')),
('slug', autoslug.fields.AutoSlugField(editable=False, populate_from='title', unique=True)),
('description', models.TextField(blank=True, verbose_name='Description')),
('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sites.Site')),
('speakers', models.ManyToManyField(through='proposals.Speach', to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Topic',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=128, unique=True, verbose_name='Name')),
('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sites.Site')),
],
),
migrations.AddField(
model_name='talk',
name='topics',
field=models.ManyToManyField(blank=True, to='proposals.Topic'),
),
migrations.AddField(
model_name='speach',
name='talk',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='proposals.Talk'),
),
migrations.AddField(
model_name='speach',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
migrations.AlterUniqueTogether(
name='speach',
unique_together=set([('user', 'talk'), ('order', 'talk')]),
),
]

View File

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.6 on 2016-06-02 21:52
from __future__ import unicode_literals
import autoslug.fields
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('proposals', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='topic',
name='slug',
field=autoslug.fields.AutoSlugField(default='dudule', editable=False, populate_from='name', unique=True),
preserve_default=False,
),
]

View File

53
proposals/models.py Normal file
View File

@ -0,0 +1,53 @@
from django.db import models
from django.contrib.sites.models import Site
from autoslug import AutoSlugField
from accounts.models import User
__all__ = [ 'Topic', 'Talk', 'Speach' ]
class Topic(models.Model):
site = models.ForeignKey(Site, on_delete=models.CASCADE)
name = models.CharField(max_length=128, verbose_name='Name', unique=True)
slug = AutoSlugField(populate_from='name', unique=True)
def __str__(self):
return self.name
class Talk(models.Model):
site = models.ForeignKey(Site, on_delete=models.CASCADE)
speakers = models.ManyToManyField(User, through='Speach')
title = models.CharField(max_length=128, verbose_name='Title')
slug = AutoSlugField(populate_from='title', unique=True)
description = models.TextField(blank=True, verbose_name='Description')
topics = models.ManyToManyField(Topic, blank=True)
def __str__(self):
return self.title
class Speach(models.Model):
SPEAKER_NO = ((1, "1"), (2, "2"), (3, "3"), (4, "4"), (5, "5"), (6, "6"), (7, "7"),)
user = models.ForeignKey(User, on_delete=models.CASCADE)
talk = models.ForeignKey(Talk, on_delete=models.CASCADE)
order = models.IntegerField(choices=SPEAKER_NO)
class Meta:
ordering = [ 'order' ]
unique_together = (
('user', 'talk'),
('order', 'talk'),
)
def __str__(self):
return self.user.username + ' speaking at ' + self.talk.title + ' in position %d' % self.order

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% block hometab %} class="active"{% endblock %}
{% block content %}
<div class="h1">Welcome to {{ site.name }}!</div>
Some general informations about the conference {{ site.name }}.
{% endblock %}

View File

@ -0,0 +1,37 @@
{% extends 'base.html' %}
{% block talktab %} class="active"{% endblock %}
{% block content %}
<h1>{{ talk.title }}</h1>
<a class="btn btn-primary" href="{% url 'edit-talk' talk.slug %}">edit</a><br />
<b>Description:</b>
<p>{{ talk.description }}</p>
<b>Speakers:</b>
<ul>
{% for speaker in talk.speakers.all %}
<li>
<a href="{% url 'show-user' speaker.username %}">
{% firstof speaker.get_full_name speaker.username %}
</a>
</li>
{% empty %}
<li><i>No speaker.</i></li>
{% endfor %}
</ul>
<b>Topics:</b>
<ul>
{% for topic in talk.topics.all %}
<li><a href="{% url 'list-talks-by-topic' topic.slug %}">{{ topic }}</a></li>
{% endfor %}
</ul>
{% endblock %}

View File

@ -0,0 +1,19 @@
{% extends 'base.html' %}
{% load bootstrap3 %}
{% block talktab %} class="active"{% endblock %}
{% block content %}
<h1>Propose a talk</h1>
<form method="POST" role="form">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<button type="submit" class="btn btn-primary">Submit</button>
{% endbuttons %}
</form>
{% endblock %}

View File

@ -0,0 +1,24 @@
{% extends 'base.html' %}
{% block talktab %} class="active"{% endblock %}
{% block content %}
<h1>{{ title }}</h1>
<ul>
{% for talk in talks %}
<li>
<a href="{% url 'show-talk' talk.slug %}">{{ talk }}</a>
<i>by</i>
{% for speach in talk.speach_set.all %}
<a href="{% url 'show-user' speach.user.username %}">{% firstof speach.user.get_full_name speach.user.username %}</a>
{% if forloop.revcounter == 2 %} and {% elif not forloop.last %}, {% endif %}
{% endfor %}
</li>
{% empty %}
<li><i>No talk.</i></li>
{% endfor %}
</ul>
{% endblock %}

View File

@ -0,0 +1,44 @@
{% extends 'base.html' %}
{% block talktab %} class="active"{% endblock %}
{% block content %}
<a class="btn btn-success" href="{% url 'add-talk' %}">Propose a talk</a>
<div class="h3">My participing talks:</div>
<ul>
{% for talk in my_talks %}
<li>
<a href="{% url 'show-talk' talk.slug %}">{{ talk }}</a>
<i>by</i>
{% for speach in talk.speach_set.all %}
<a href="{% url 'show-user' speach.user.username %}">{% firstof speach.user.get_full_name speach.user.username %}</a>
{% if forloop.revcounter == 2 %} and {% elif not forloop.last %}, {% endif %}
{% endfor %}
<a class="btn btn-primary" href="{% url 'edit-talk' talk.slug %}">edit</a>
</li>
{% empty %}
<li>No proposed talk.</li>
{% endfor %}
</ul>
<div class="h3">Others talks:</div>
<ul>
{% for talk in other_talks %}
<li>
<a href="{% url 'show-talk' talk.slug %}">{{ talk }}</a>
<i>by</i>
{% for speach in talk.speach_set.all %}
<a href="{% url 'show-user' speach.user.username %}">{% firstof speach.user.get_full_name speach.user.username %}</a>
{% if forloop.revcounter == 2 %} and {% elif not forloop.last %}, {% endif %}
{% endfor %}
</li>
{% empty %}
<li>No other talk.</li>
{% endfor %}
</ul>
{% endblock %}

View File

@ -0,0 +1,19 @@
{% extends 'base.html' %}
{% block topictab %} class="active"{% endblock %}
{% block content %}
<h1>Topics:</h1>
<ul>
{% for topic in topics %}
<li>
<a href="{% url 'list-talks-by-topic' topic.slug %}">{{ topic.name }}</a>
</li>
{% empty %}
<li><i>No topic.</i></li>
{% endfor %}
</ul>
{% endblock %}

View File

@ -0,0 +1,12 @@
{% extends 'base.html' %}
{% block content %}
<h1>{% firstof user.get_full_name user.username %}</h1>
<b>Biography:</b>
<p>{{ user.biography }}</p>
<a href="{% url 'list-talks-by-speaker' user.username %}">See the list of talks with this speaker</a>
{% endblock %}

3
proposals/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

16
proposals/urls.py Normal file
View File

@ -0,0 +1,16 @@
from django.conf.urls import url
from proposals import views
urlpatterns = [
url(r'^$', views.home, name='home'),
url(r'^talk/$', views.talk_list, name='list-talks'),
url(r'^talk/add/$', views.talk_edit, name='add-talk'),
url(r'^talk/edit/(?P<talk>[-\w]+)$', views.talk_edit, name='edit-talk'),
url(r'^talk/details/(?P<talk>[-\w]+)$', views.talk_details, name='show-talk'),
url(r'^talk/by-topic/(?P<topic>[-\w]+)$', views.talk_list_by_topic, name='list-talks-by-topic'),
url(r'^talk/by-speaker/(?P<speaker>[\w.@+-]+)$', views.talk_list_by_speaker, name='list-talks-by-speaker'),
url(r'^topic/$', views.topic_list, name='list-topics'),
url(r'^user/(?P<username>[\w.@+-]+)$', views.user_details, name='show-user'),
]

87
proposals/views.py Normal file
View File

@ -0,0 +1,87 @@
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.sites.shortcuts import get_current_site
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.core.exceptions import PermissionDenied
from accounts.models import *
from proposals.models import *
from proposals.forms import *
def home(request):
return render(request, 'proposals/home.html')
@login_required
def talk_list(request):
talks = Talk.objects.filter(site=get_current_site(request))
mine = talks.filter(speakers=request.user)
others = talks.exclude(speakers=request.user)
return render(request, 'proposals/talks.html', {
'my_talks': mine,
'other_talks': others,
})
@login_required
def talk_list_by_topic(request, topic):
topic = get_object_or_404(Topic, site=get_current_site(request), slug=topic)
talks = Talk.objects.filter(topics=topic)
return render(request, 'proposals/talk_list.html', {
'title': 'Talks related to %s:' % topic.name,
'talks': talks,
})
@login_required
def talk_list_by_speaker(request, speaker):
speaker = get_object_or_404(User, username=speaker)
talks = Talk.objects.filter(site=get_current_site(request), speakers=speaker)
return render(request, 'proposals/talk_list.html', {
'title': 'Talks with %s:' % (speaker.get_full_name() or speaker.username),
'talks': talks,
})
@login_required
def talk_edit(request, talk=None):
if talk:
talk = get_object_or_404(Talk, slug=talk)
if talk.site != get_current_site(request):
raise PermissionDenied()
if not request.user.is_superuser and not talk.speakers.filter(username=request.user.username).exists(): # FIXME fine permissions
raise PermissionDenied()
form = TalkForm(request.POST or None, instance=talk)
if request.method == 'POST' and form.is_valid():
if talk:
talk = form.save()
messages.success(request, 'Talk modified successfully!')
else:
talk = form.save(commit=False)
talk.site = get_current_site(request)
talk.save()
speach = Speach(user=request.user,talk=talk,order=1)
speach.save()
messages.success(request, 'Talk proposed successfully!')
return redirect('show-talk', talk.slug)
return render(request, 'proposals/talk_edit.html', {
'form': form,
})
@login_required
def talk_details(request, talk):
talk = get_object_or_404(Talk, slug=talk)
return render(request, 'proposals/talk_details.html', {
'talk': talk,
})
@login_required
def topic_list(request):
topics = Topic.objects.filter(site=get_current_site(request))
return render(request, 'proposals/topic_list.html', {
'topics': topics,
})
@login_required
def user_details(request, username):
user = get_object_or_404(User, username=username)
return render(request, 'proposals/user_details.html', {
'user': user,
})

4
requirements.txt Normal file
View File

@ -0,0 +1,4 @@
django<1.10
django-bower
django-bootstrap3
django-autoslug