Improved attribution template and adapted data model

This commit is contained in:
Claude Paroz 2012-11-07 17:45:53 +01:00
parent d4072450fa
commit 780b475d41
11 changed files with 196 additions and 82 deletions

View file

@ -21,10 +21,11 @@ urlpatterns = patterns('',
# AJAX/JSON urls
url(r'^section/(?P<pk>\d+)/periods/', 'stages.views.section_periods'),
url(r'^period/(?P<pk>\d+)/students/', 'stages.views.period_students'),
url(r'^period/(?P<pk>\d+)/corporations/', 'stages.views.period_corporations'),
url(r'^period/(?P<pk>\d+)/corporations/', 'stages.views.period_availabilities'),
# Training params in POST:
url(r'^training/new/', 'stages.views.new_training'),
url(r'^training/by_period/(?P<pk>\d+)/', views.TrainingsByPeriodView.as_view()),
url(r'^student/(?P<pk>\d+)/summary/', views.StudentSummaryView.as_view()),
url(r'^corporation/(?P<pk>\d+)/summary/', views.CorporationSummaryView.as_view()),
url(r'^availability/(?P<pk>\d+)/summary/', views.AvailabilitySummaryView.as_view()),
)

Binary file not shown.

View file

@ -25,14 +25,20 @@ class CorporationAdmin(admin.ModelAdmin):
inlines = [ContactInline]
class AvailabilityInline(admin.TabularInline):
model = Availability
extra = 1
class PeriodAdmin(admin.ModelAdmin):
list_display = ('dates', 'section')
list_filter = ('section',)
inlines = [AvailabilityInline]
class AvailabilityAdmin(admin.ModelAdmin):
list_display = ('corporation', 'period', 'number')
fields = (('corporation', 'period'), ('number', 'domain'))
list_display = ('corporation', 'period', 'domain')
list_filter = ('period',)
fields = (('corporation', 'period'), 'domain', 'comment')
admin.site.register(Student, StudentAdmin)

View file

@ -24,48 +24,48 @@
"pk": 1,
"model": "stages.student",
"fields": {
"birth_date": "1994-05-12",
"city": "La Chaux-de-Fonds",
"first_name": "Albin",
"last_name": "Dupond",
"section": 1,
"pcode": "2300",
"city": "La Chaux-de-Fonds"
"last_name": "Dupond",
"section": 1,
"pcode": "2300",
"birth_date": "1994-05-12"
}
},
{
"pk": 2,
"model": "stages.student",
"fields": {
"birth_date": "1994-07-12",
"city": "Neuch\u00e2tel",
"first_name": "Justine",
"last_name": "Varrin",
"section": 1,
"pcode": "2000",
"city": "Neuchâtel"
"last_name": "Varrin",
"section": 1,
"pcode": "2000",
"birth_date": "1994-07-12"
}
},
{
"pk": 3,
"model": "stages.student",
"fields": {
"birth_date": "1994-05-20",
"city": "Cernier",
"first_name": "Elvire",
"last_name": "Hickx",
"section": 1,
"pcode": "2053",
"city": "Cernier"
"last_name": "Hickx",
"section": 1,
"pcode": "2053",
"birth_date": "1994-05-20"
}
},
{
"pk": 4,
"model": "stages.student",
"fields": {
"birth_date": "1994-10-11",
"first_name": "André",
"last_name": "Allemand",
"section": 1,
"pcode": "2314",
"city": "La Sagne"
"city": "La Sagne",
"first_name": "Andr\u00e9",
"last_name": "Allemand",
"section": 1,
"pcode": "2314",
"birth_date": "1994-10-11"
}
},
{
@ -88,6 +88,17 @@
"email": ""
}
},
{
"pk": 1,
"model": "stages.corpcontact",
"fields": {
"corporation": 1,
"first_name": "Jean",
"last_name": "Horner",
"tel": "",
"email": ""
}
},
{
"pk": 1,
"model": "stages.domain",
@ -113,13 +124,32 @@
},
{
"pk": 1,
"model": "stages.training",
"model": "stages.availability",
"fields": {
"corporation": 1,
"domain": 1,
"period": 1,
"comment": ""
}
},
{
"pk": 2,
"model": "stages.availability",
"fields": {
"corporation": 1,
"domain": 2,
"period": 1,
"comment": ""
}
},
{
"pk": 1,
"model": "stages.training",
"fields": {
"availability": 1,
"student": 1,
"referent": 1
"referent": 1,
"comment": ""
}
}
]

View file

@ -101,26 +101,33 @@ class Availability(models.Model):
""" Disponibilités des institutions """
corporation = models.ForeignKey(Corporation, verbose_name='Institution')
period = models.ForeignKey(Period, verbose_name='Période')
number = models.IntegerField(verbose_name='Nombre de places')
domain = models.ForeignKey(Domain, verbose_name='Domaine')
comment = models.TextField(blank=True, verbose_name='Remarques')
class Meta:
verbose_name = "Disponibilité"
def __unicode__(self):
return '%d place(s) chez %s (%s)' % (self.number, self.corporation, self.period)
return '%s - %s (%s)' % (self.period, self.corporation, self.domain)
@property
def free(self):
try:
self.training
except Training.DoesNotExist:
return True
return False
class Training(models.Model):
""" Stages """
student = models.ForeignKey(Student, verbose_name='Étudiant')
corporation = models.ForeignKey(Corporation, verbose_name='Institution')
availability = models.OneToOneField(Availability, verbose_name='Disponibilité')
referent = models.ForeignKey(Referent, verbose_name='Référent')
period = models.ForeignKey(Period, verbose_name='Période')
domain = models.ForeignKey(Domain, verbose_name='Domaine')
comment = models.TextField(blank=True, verbose_name='Remarques')
class Meta:
verbose_name = "Stage"
def __unicode__(self):
return '%s chez %s (%s)' % (self.student, self.corporation, self.period)
return '%s chez %s (%s)' % (self.student, self.availability.corporation, self.availability.period)

View file

@ -6,10 +6,10 @@ import json
from django.http import HttpResponse, HttpResponseNotAllowed
from django.shortcuts import get_object_or_404
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import DetailView, TemplateView
from django.views.generic import DetailView, TemplateView, ListView
from .forms import PeriodForm
from .models import Section, Student, Corporation, Period, Training
from .models import Section, Student, Corporation, Period, Training, Referent, Availability
class StudentSummaryView(DetailView):
@ -17,9 +17,18 @@ class StudentSummaryView(DetailView):
template_name = 'student_summary.html'
class CorporationSummaryView(DetailView):
model = Corporation
template_name = 'corporation_summary.html'
class AvailabilitySummaryView(DetailView):
model = Availability
template_name = 'availability_summary.html'
class TrainingsByPeriodView(ListView):
template_name = 'trainings_list.html'
context_object_name = 'trainings'
def get_queryset(self):
return Training.objects.select_related('student', 'availability__corporation', 'availability__domain'
).filter(availability__period__pk=self.kwargs['pk'])
class AttributionView(TemplateView):
@ -30,6 +39,7 @@ class AttributionView(TemplateView):
context.update({
#'period_form': PeriodForm(),
'sections': Section.objects.all(),
'referents': Referent.objects.all(),
})
return context
@ -47,14 +57,14 @@ def period_students(request, pk):
"""
period = get_object_or_404(Period, pk=pk)
students = period.section.student_set.all().order_by('last_name')
trainings = dict((t.student_id, t.id) for t in Training.objects.filter(period=period))
trainings = dict((t.student_id, t.id) for t in Training.objects.filter(availability__period=period))
data = [{'name': unicode(s), 'id': s.id, 'training_id': trainings.get(s.id)} for s in students]
return HttpResponse(json.dumps(data), content_type="application/json")
def period_corporations(request, pk):
""" Return all corporations with availabilities in the specified period """
def period_availabilities(request, pk):
""" Return all availabilities in the specified period """
period = get_object_or_404(Period, pk=pk)
corps = [(av.corporation.id, av.corporation.name)
corps = [{'id': av.id, 'corp_name': av.corporation.name, 'domain': av.domain.name, 'free': av.free}
for av in period.availability_set.select_related('corporation').all()]
return HttpResponse(json.dumps(corps), content_type="application/json")
@ -62,11 +72,14 @@ def period_corporations(request, pk):
def new_training(request):
if request.method != 'POST':
return HttpResponseNotAllowed()
training = Training.objects.create(
period=Period.objects.get(pk=request.POST.get('period')),
student=Student.objects.get(pk=request.POST.get('student')),
corporation=Corporation.objects.get(pk=request.POST.get('corp'))
)
try:
training = Training.objects.create(
student=Student.objects.get(pk=request.POST.get('student')),
availability=Availability.objects.get(pk=request.POST.get('avail')),
referent=Referent.objects.get(pk=request.POST.get('referent')),
)
except Exception as exc:
return HttpResponse(str(exc))
return HttpResponse('OK')
@ -77,10 +90,10 @@ def stages_export(request):
export_fields = [
('Prénom', 'student__first_name'), ('Nom', 'student__last_name'),
('Filière', 'period__section__name'),
('Début', 'period__start_date'), ('Fin', 'period__end_date'),
('Institution', 'corporation__name'),
('Domaine', 'domain__name'),
('Filière', 'student__section__name'),
('Début', 'availability__period__start_date'), ('Fin', 'availability__period__end_date'),
('Institution', 'availability__corporation__name'),
('Domaine', 'availability__domain__name'),
('Prénom référent', 'referent__first_name'), ('Nom référent', 'referent__last_name')
]

View file

@ -46,7 +46,10 @@
</table>
</div>
{% endfor %}
<p><a href="{% url 'stages_export' %}">Exporter les données de stages</a></p>
<ul>
<li><a href="{% url 'attribution' %}">Attributions des stages</a></li>
<li><a href="{% url 'stages_export' %}">Exporter les données de stages</a></li>
</ul>
{% else %}
<p>{% trans "You don't have permission to edit anything." %}</p>
{% endif %}

View file

@ -1,23 +1,28 @@
{% extends "admin/base_site.html" %}
{% load admin_static %}
{% load i18n admin_static %}
{% load url from future %}
{% block extrastyle %}
<style>
div#period_choice { margin: 0 auto; text-align: center;}
div#period_choice { margin: 0 auto; text-align: center; width: 60%; padding: 0.5em; margin-bottom: 1em; background-color: #EDF3FE;}
select#section_select { width: 8em; margin-right: 2em; }
select#period_select { width: 16em; }
div#student_choice { float: left; }
select#student_select { width: 16em; }
div#student_choice { float: left; width: 18%; }
select#student_select { width: 100%; }
div#corp_choice { float: right; }
select#corp_select { width: 16em; }
div#corp_choice { float: right; width: 18%; }
select#corp_select { width: 100%; }
div#student_detail { float:left; width: 30%; margin: 1em; }
div#corp_detail { float:left; width: 30%; margin: 1em; }
div#student_detail { float:left; width: 40%; margin: 1em; padding: 0.5em; border: 3px solid red; min-height: 4em; border-radius: 8px; }
div#corp_detail { float:right; width: 40%; margin: 1em; padding: 0.5em; border: 3px solid red; min-height: 4em; border-radius: 8px; }
div.filled { border-color: green !important; }
div#training_form { text-align: center; }
div#buttons_div { margin-top: 1em; }
input#valid_training { display: none; }
div#trainings { clear: both; padding-top: 1em; }
</style>
{% endblock %}
@ -40,32 +45,37 @@ function update_periods(section_id) {
function update_students(period_id) {
$('#student_select').find('option').remove();
$('#student_detail').html('');
$('#student_detail').html('').removeClass("filled");
current_student = null;
$('input#valid_training').hide()
if (period_id == '') return;
$.getJSON('/period/' + period_id + '/students/', function(data) {
var sel = $('#student_select');
$.each(data, function() {
sel.append($("<option />").val(this.id).text(this.name));
if (this.training_id == null) sel.append($("<option />").val(this.id).text(this.name));
})
});
}
function update_corporations(period_id) {
$('#corp_select').find('option').remove();
$('#corp_detail').html('');
current_corp = null;
$('#corp_detail').html('').removeClass("filled");
current_avail = null;
$('input#valid_training').hide()
if (period_id == '') return;
$.getJSON('/period/' + period_id + '/corporations/', function(data) {
var sel = $('#corp_select');
$.each(data, function() {
sel.append($("<option />").val(this[0]).text(this[1]));
if (this.free) sel.append($("<option />").val(this.id).text(this.corp_name));
})
});
}
function update_trainings(period_id) {
if (period_id == '') $('ul#training_list').html('');
else $('ul#training_list').load('/training/by_period/' + period_id + '/');
}
$(document).ready(function() {
$('#section_select').change(function(ev) {
// Update period list when section is modified
@ -77,36 +87,59 @@ $(document).ready(function() {
// Update student/corporation list when period is modified
update_students($(this).val());
update_corporations($(this).val());
update_trainings($(this).val());
});
$('#student_select').change(function(ev) {
$('#student_detail').load('/student/' + $(this).val() + '/summary/');
$('#student_detail').load('/student/' + $(this).val() + '/summary/').addClass("filled");
current_student = $(this).val();
if (current_corp !== null) $('input#valid_training').show()
if (current_avail !== null) $('input#valid_training').show()
});
$('#corp_select').change(function(ev) {
$('#corp_detail').load('/corporation/' + $(this).val() + '/summary/');
current_corp = $(this).val();
$('#corp_detail').load('/availability/' + $(this).val() + '/summary/').addClass("filled");
current_avail = $(this).val();
if (current_student !== null) $('input#valid_training').show()
});
$('#valid_training').click(function() {
$.post('/training/new/', {period: $('#period_select').val(), student: current_student, corp: current_corp},
$.post('/training/new/', {
student: current_student, avail: current_avail,
referent: $('#referent_select').val()},
function(data) {
// On response: remove student from list, remove corp if no more avails
if (data == 'OK') alert("OK");
});
if (data != 'OK') {
alert(data);
return;
}
// Clear selected student/corp
$('ul#training_list').append('<li>' + $('option:selected', '#student_select').html() + ' - ' + $('option:selected', '#corp_select').html() + '</li>');
$('option:selected', '#student_select').remove();
$('#student_detail').html('').removeClass("filled");
$('option:selected', '#corp_select').remove();
$('#corp_detail').html('').removeClass("filled");
current_student = null;
current_avail = null;
$('input#valid_training').hide();
$('#referent_select').val('');
}
);
});
update_periods($('#section_select').val());
});
var current_student = null;
var current_corp = null;
var current_avail = null;
</script>
{% endblock %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
&rsaquo; Attributions
</div>
{% endblock %}
{% block content %}
<div id="period_choice">
<form>
@ -130,8 +163,25 @@ var current_corp = null;
</div>
<div id="training_main">
<div id="student_detail"></div>
<div id="corp_detail"></div>
<div id="training_form"><input id="valid_training" type="button" value="Valider ce stage"></div>
<div style="overflow: auto;">
<div id="student_detail"></div>
<div id="corp_detail"></div>
</div>
<div id="training_form">
<form>
<div id="referent_choice"><label for="referent_select">Référent:</label>
<select id="referent_select"><option value="">-------</option>
{% for ref in referents %}<option value="{{ ref.id }}">{{ ref }}</option>{% endfor %}</select>
</div>
<div id="buttons_div"><input id="valid_training" type="button" value="Valider ce stage"></div>
</form>
</div>
</div>
<div id="trainings">
<h3>Stages planifiés pour la période choisie</h3>
<ul id="training_list">-
</ul>
</div>
{% endblock %}

View file

@ -0,0 +1,6 @@
<p><i>Domaine:</i> {{ object.domain }}</p>
{{ object.corporation.name }}<br>
{{ object.corporation.street }}<br>
{{ object.corporation.pcode }} {{ object.corporation.city }}<br>
Tél: {{ object.corporation.tel }}<br>
Courriel: {{ object.corporation.courriel }}

View file

@ -1,5 +0,0 @@
{{ object.name }}<br>
{{ object.street }}<br>
{{ object.pcode }} {{ object.city }}<br>
Tél: {{ object.tel }}<br>
Courriel: {{ object.courriel }}

View file

@ -0,0 +1,3 @@
{% for obj in trainings %}
<li>{{ obj.student }} - {{ obj.availability.corporation }} - {{ obj.availability.domain }}</li>
{% endfor %}