first commit
commit
f6e04013e3
@ -0,0 +1,4 @@
|
||||
/venv/
|
||||
*.pyc
|
||||
*.swp
|
||||
*.sqlite3
|
@ -0,0 +1,3 @@
|
||||
[submodule "components"]
|
||||
path = components
|
||||
url = git://github.com/toulibre/ponyconf-components
|
@ -0,0 +1,6 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from accounts.models import *
|
||||
|
||||
|
||||
admin.site.register(User)
|
@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AccountsConfig(AppConfig):
|
||||
name = 'accounts'
|
@ -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',)
|
@ -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()),
|
||||
],
|
||||
),
|
||||
]
|
@ -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'),
|
||||
),
|
||||
]
|
@ -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')
|
@ -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 %}
|
@ -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 %}
|
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
@ -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'),
|
||||
]
|
@ -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,
|
||||
})
|
@ -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
|
@ -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,0 +1,8 @@
|
||||
from django.contrib.sites.shortcuts import get_current_site
|
||||
|
||||
|
||||
def site(request):
|
||||
|
||||
return {
|
||||
'site': get_current_site(request),
|
||||
}
|
@ -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,
|
||||
}
|
@ -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> 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> 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>
|
@ -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')),
|
||||
]
|
@ -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,0 +1,8 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from proposals.models import *
|
||||
|
||||
|
||||
admin.site.register(Topic)
|
||||
admin.site.register(Talk)
|
||||
admin.site.register(Speach)
|
@ -0,0 +1,5 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ProposalsConfig(AppConfig):
|
||||
name = 'proposals'
|
@ -0,0 +1,9 @@
|
||||
from django.forms.models import modelform_factory
|
||||
|
||||
from proposals.models import *
|
||||
|
||||
|
||||
__all__ = ['TalkForm']
|
||||
|
||||
|
||||
TalkForm = modelform_factory(Talk, fields=['title', 'description'])
|
@ -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')]),
|
||||
),
|
||||
]
|
@ -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,
|
||||
),
|
||||
]
|
@ -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
|
@ -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 %}
|
@ -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 %}
|
@ -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 %}
|
@ -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 %}
|
@ -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 %}
|
@ -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 %}
|
@ -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 %}
|
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
@ -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'),
|
||||
]
|
@ -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,
|
||||
})
|
@ -0,0 +1,4 @@
|
||||
django<1.10
|
||||
django-bower
|
||||
django-bootstrap3
|
||||
django-autoslug
|
Loading…
Reference in New Issue