Improved attribution template and adapted data model
This commit is contained in:
parent
d4072450fa
commit
780b475d41
11 changed files with 196 additions and 82 deletions
|
|
@ -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.
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
› 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 %}
|
||||
|
|
|
|||
6
templates/availability_summary.html
Normal file
6
templates/availability_summary.html
Normal 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 }}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
{{ object.name }}<br>
|
||||
{{ object.street }}<br>
|
||||
{{ object.pcode }} {{ object.city }}<br>
|
||||
Tél: {{ object.tel }}<br>
|
||||
Courriel: {{ object.courriel }}
|
||||
3
templates/trainings_list.html
Normal file
3
templates/trainings_list.html
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{% for obj in trainings %}
|
||||
<li>{{ obj.student }} - {{ obj.availability.corporation }} - {{ obj.availability.domain }}</li>
|
||||
{% endfor %}
|
||||
Loading…
Add table
Add a link
Reference in a new issue