Demande #721.
import hashlib
from django import forms
from django.db.models import Q
+from django.forms.models import inlineformset_factory
from itertools import chain
from models import *
value = self.cleaned_data['organisme_demandeur_visible']
return bool(int(value)) if value else False
+ExpertiseFormSet = inlineformset_factory(Chercheur, Expertise, form=ExpertiseForm, extra=1)
+
class ChercheurFormGroup(object):
"""Groupe de formulaires nécessaires pour l'inscription et l'édition
d'un chercheur."""
self.publication3 = PublicationForm(data=data, prefix='publication3', instance=chercheur and chercheur.publication3)
self.publication4 = PublicationForm(data=data, prefix='publication4', instance=chercheur and chercheur.publication4)
self.these = TheseForm(data=data, prefix='these', instance=chercheur and chercheur.these)
- self.expertise = ExpertiseForm(data=data, prefix='expertise', instance=chercheur and chercheur.expertise)
+ self.expertises = ExpertiseFormSet(data=data, prefix='expertise', instance=chercheur)
@property
def has_errors(self):
return bool(self.chercheur.errors or self.personne.errors or self.groupes.errors or
self.publication1.errors or self.publication2.errors or self.publication3.errors or
- self.publication4.errors or self.these.errors or self.expertise.errors)
+ self.publication4.errors or self.these.errors or self.expertises.errors)
def is_valid(self):
return self.chercheur.is_valid() and self.personne.is_valid() and self.groupes.is_valid() and \
self.publication1.is_valid() and self.publication2.is_valid() and \
self.publication3.is_valid() and self.publication4.is_valid() and \
- self.these.is_valid() and self.expertise.is_valid()
+ self.these.is_valid() and self.expertises.is_valid()
def save(self):
if self.is_valid():
if self.publication4.cleaned_data['titre']:
chercheur.publication4 = self.publication4.save()
chercheur.these = self.these.save()
- if self.expertise.cleaned_data['nom']:
- chercheur.expertise = self.expertise.save()
+ self.expertises.save()
# Puis enregistrer le chercheur lui-même.
self.chercheur.save()
statut = self.cleaned_data["statut"]
if statut:
if statut == "expert":
- qs = qs.exclude(expertise=None)
+ qs = qs.exclude(expertises=None)
else:
qs = qs.filter(statut=statut)
pays = self.cleaned_data["pays"]
discipline = models.ForeignKey(Discipline, db_column='discipline', null=True, verbose_name='Discipline')
theme_recherche = models.TextField(null=True, blank=True, verbose_name='thème de recherche')
groupe_recherche = models.CharField(max_length=255, blank=True, verbose_name='groupe de recherche')
- expertise = models.ForeignKey('Expertise', db_column='expertise', null=True, blank=True, related_name='expertise')
url_site_web = models.URLField(max_length=255, null=True, blank=True, verbose_name='adresse site Internet')
url_blog = models.URLField(max_length=255, null=True, blank=True, verbose_name='blog')
url_reseau_social = models.URLField(
class Expertise(models.Model):
id = models.AutoField(primary_key=True, db_column='id')
- nom = models.CharField(max_length=255, null=True, blank=True, verbose_name = 'Objet de la dernière expertise')
+ chercheur = models.ForeignKey(Chercheur, related_name='expertises')
+ nom = models.CharField(max_length=255, null=True, blank=True, verbose_name = "Objet de l'expertise")
date = models.CharField(max_length=255, blank=True)
- lieu = models.CharField(max_length=255, null=True, blank=True, verbose_name = 'Lieu de la dernière expertise')
+ lieu = models.CharField(max_length=255, null=True, blank=True, verbose_name = "Lieu de l'expertise")
organisme_demandeur = models.CharField(max_length=255, null=True, blank=True, verbose_name = 'Organisme demandeur')
organisme_demandeur_visible = models.BooleanField(verbose_name="Afficher l'organisme demandeur")
actif = models.BooleanField(editable = False, db_column='actif')
td ul { margin: 0 }
+form { padding-right:20px; margin: 1em 0; }
form td { vertical-align: top }
form th { width: 20em; text-align: left; font-weight: bold }
form table { width: 100% }
form input.date { width: auto }
form select { width: 80%; overflow: hidden }
form p { margin-bottom: 2px }
+fieldset { clear: both; padding: 10px; margin: 0 0 0 0; position: relative }
+fieldset { border-color: #000000; border-width: 1px 0 0 0; border-style: solid none none none; }
+fieldset { font-size: 100%; }
+fieldset fieldset { border: 1px solid #ccc; background: #fafafa; margin: 10px }
+legend { font-size: 150%; font-weight: bold; color: #000000; margin: 0 0 0 0; padding: 0 5px; }
+
+label {
+ font-size: 12px;
+}
+td, th
+{
+vertical-align:middle;
+}
+
.box { padding:0 0 20px 0; }
.lbl {color: #97012c; }
#contenu .demi-gauche img.top { height:9px; position:relative; top:-9px;}
#contenu .demi-gauche img.bottom { height:9px; position:relative; bottom:-9px;}
-.contenu-wrapper { padding:0 0 0 25px; }
+.contenu-wrapper { padding: 0 25px; }
ul a { text-decoration:none; }
ul a:hover { text-decoration:underline; }
#agenda, #actualites {position: relative;}
#rss-agenda, #rss-actualites {position: absolute; right: 26px; top: 10px;}
-form {padding-right:20px; margin: 1em 0; }
-
-form fieldset {
- clear: both;
- font-size: 100%;
- border-color: #000000;
- border-width: 1px 0 0 0;
- border-style: solid none none none;
- padding: 10px;
- margin: 0 0 0 0;
-}
-
-form fieldset legend {
- font-size: 150%;
- font-weight: bold;
- color: #000000;
- margin: 0 0 0 0;
- padding: 0 5px;
-}
-
-label {
- font-size: 12px;
-}
-td, th
-{
-vertical-align:middle;
-}
-
.infotip
{
width:300px;
.horizontal-radio-buttons ul { margin-left: 0 }
.horizontal-radio-buttons li { display: inline }
+
+.delete-row { position: absolute; top: 10px; right: 10px }
+.add-row { float: right; margin-right: 21px }
--- /dev/null
+$(document).ready(function() {
+ $('#expertises fieldset').formset({
+ prefix: 'expertise',
+ addText: 'ajouter une expertise',
+ deleteText: 'supprimer cette expertise',
+ formCssClass: 'dynamic-form-expertises'
+ });
+});
--- /dev/null
+/**\r
+ * jQuery Formset 1.1\r
+ * @author Stanislaus Madueke (stan DOT madueke AT gmail DOT com)\r
+ * @requires jQuery 1.2.6 or later\r
+ *\r
+ * Copyright (c) 2009, Stanislaus Madueke\r
+ * All rights reserved.\r
+ *\r
+ * Licensed under the New BSD License\r
+ * See: http://www.opensource.org/licenses/bsd-license.php\r
+ */\r
+;(function($) {\r
+ $.fn.formset = function(opts)\r
+ {\r
+ var options = $.extend({}, $.fn.formset.defaults, opts),\r
+ flatExtraClasses = options.extraClasses.join(' '),\r
+ $$ = $(this),\r
+\r
+ applyExtraClasses = function(row, ndx) {\r
+ if (options.extraClasses) {\r
+ row.removeClass(flatExtraClasses);\r
+ row.addClass(options.extraClasses[ndx % options.extraClasses.length]);\r
+ }\r
+ },\r
+\r
+ updateElementIndex = function(elem, prefix, ndx) {\r
+ var idRegex = new RegExp('(' + prefix + '-\\d+-)|(^)'),\r
+ replacement = prefix + '-' + ndx + '-';\r
+ if (elem.attr("for")) elem.attr("for", elem.attr("for").replace(idRegex, replacement));\r
+ if (elem.attr('id')) elem.attr('id', elem.attr('id').replace(idRegex, replacement));\r
+ if (elem.attr('name')) elem.attr('name', elem.attr('name').replace(idRegex, replacement));\r
+ },\r
+\r
+ hasChildElements = function(row) {\r
+ return row.find('input,select,textarea,label').length > 0;\r
+ },\r
+\r
+ insertDeleteLink = function(row) {\r
+ if (row.is('TR')) {\r
+ // If the forms are laid out in table rows, insert\r
+ // the remove button into the last table cell:\r
+ row.children(':last').append('<a class="' + options.deleteCssClass +'" href="javascript:void(0)">' + options.deleteText + '</a>');\r
+ } else if (row.is('UL') || row.is('OL')) {\r
+ // If they're laid out as an ordered/unordered list,\r
+ // insert an <li> after the last list item:\r
+ row.append('<li><a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText +'</a></li>');\r
+ } else {\r
+ // Otherwise, just insert the remove button as the\r
+ // last child element of the form's container:\r
+ row.append('<a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText +'</a>');\r
+ }\r
+ row.find('a.' + options.deleteCssClass).click(function() {\r
+ var row = $(this).parents('.' + options.formCssClass),\r
+ del = row.find('input:hidden[id $= "-DELETE"]');\r
+ if (del.length) {\r
+ // We're dealing with an inline formset; rather than remove\r
+ // this form from the DOM, we'll mark it as deleted and hide\r
+ // it, then let Django handle the deleting:\r
+ del.val('on');\r
+ row.hide();\r
+ } else {\r
+ row.remove();\r
+ // Update the TOTAL_FORMS form count.\r
+ // Also update names and IDs for all remaining form controls so they remain in sequence:\r
+ var forms = $('.' + options.formCssClass).not('.formset-custom-template');\r
+ $('#id_' + options.prefix + '-TOTAL_FORMS').val(forms.length);\r
+ for (var i=0, formCount=forms.length; i<formCount; i++) {\r
+ applyExtraClasses(forms.eq(i), i);\r
+ forms.eq(i).find('input,select,textarea,label').each(function() {\r
+ updateElementIndex($(this), options.prefix, i);\r
+ });\r
+ }\r
+ }\r
+ // If a post-delete callback was provided, call it with the deleted form:\r
+ if (options.removed) options.removed(row);\r
+ return false;\r
+ });\r
+ };\r
+\r
+ $$.each(function(i) {\r
+ var row = $(this),\r
+ del = row.find('input:checkbox[id $= "-DELETE"]');\r
+ if (del.length) {\r
+ // If you specify "can_delete = True" when creating an inline formset,\r
+ // Django adds a checkbox to each form in the formset.\r
+ // Replace the default checkbox with a hidden field:\r
+ del.before('<input type="hidden" name="' + del.attr('name') +'" id="' + del.attr('id') +'" />');\r
+ del.remove();\r
+ }\r
+ if (hasChildElements(row)) {\r
+ insertDeleteLink(row);\r
+ row.addClass(options.formCssClass);\r
+ applyExtraClasses(row, i);\r
+ }\r
+ });\r
+\r
+ if ($$.length) {\r
+ var addButton, template;\r
+ if (options.formTemplate) {\r
+ // If a form template was specified, we'll clone it to generate new form instances:\r
+ template = (options.formTemplate instanceof $) ? options.formTemplate : $(options.formTemplate);\r
+ template.removeAttr('id').addClass(options.formCssClass).addClass('formset-custom-template');\r
+ template.find('input,select,textarea,label').each(function() {\r
+ updateElementIndex($(this), options.prefix, 2012);\r
+ });\r
+ insertDeleteLink(template);\r
+ } else {\r
+ // Otherwise, use the last form in the formset; this works much better if you've got\r
+ // extra (>= 1) forms (thnaks to justhamade for pointing this out):\r
+ template = $('.' + options.formCssClass + ':last').clone(true).removeAttr('id');\r
+ template.find('input:hidden[id $= "-DELETE"]').remove();\r
+ template.find('input,select,textarea,label').each(function() {\r
+ var elem = $(this);\r
+ // If this is a checkbox or radiobutton, uncheck it.\r
+ // This fixes Issue 1, reported by Wilson.Andrew.J:\r
+ if (elem.is('input:checkbox') || elem.is('input:radio')) {\r
+ elem.attr('checked', false);\r
+ } else {\r
+ elem.val('');\r
+ }\r
+ });\r
+ }\r
+ // FIXME: Perhaps using $.data would be a better idea?\r
+ options.formTemplate = template;\r
+\r
+ if ($$.attr('tagName') == 'TR') {\r
+ // If forms are laid out as table rows, insert the\r
+ // "add" button in a new table row:\r
+ var numCols = $$.eq(0).children().length;\r
+ $$.parent().append('<tr><td colspan="' + numCols + '"><a class="' + options.addCssClass + '" href="javascript:void(0)">' + options.addText + '</a></tr>');\r
+ addButton = $$.parent().find('tr:last a');\r
+ addButton.parents('tr').addClass(options.formCssClass + '-add');\r
+ } else {\r
+ // Otherwise, insert it immediately after the last form:\r
+ $$.filter(':last').after('<a class="' + options.addCssClass + '" href="javascript:void(0)">' + options.addText + '</a>');\r
+ addButton = $$.filter(':last').next();\r
+ }\r
+ addButton.click(function() {\r
+ var formCount = parseInt($('#id_' + options.prefix + '-TOTAL_FORMS').val()),\r
+ row = options.formTemplate.clone(true).removeClass('formset-custom-template'),\r
+ buttonRow = $(this).parents('tr.' + options.formCssClass + '-add').get(0) || this;\r
+ applyExtraClasses(row, formCount);\r
+ row.insertBefore($(buttonRow)).show();\r
+ row.find('input,select,textarea,label').each(function() {\r
+ updateElementIndex($(this), options.prefix, formCount);\r
+ });\r
+ $('#id_' + options.prefix + '-TOTAL_FORMS').val(formCount + 1);\r
+ // If a post-add callback was supplied, call it with the added form:\r
+ if (options.added) options.added(row);\r
+ return false;\r
+ });\r
+ }\r
+\r
+ return $$;\r
+ }\r
+\r
+ /* Setup plugin defaults */\r
+ $.fn.formset.defaults = {\r
+ prefix: 'form', // The form prefix for your django formset\r
+ formTemplate: null, // The jQuery selection cloned to generate new form instances\r
+ addText: 'add another', // Text for the add link\r
+ deleteText: 'remove', // Text for the delete link\r
+ addCssClass: 'add-row', // CSS class applied to the add link\r
+ deleteCssClass: 'delete-row', // CSS class applied to the delete link\r
+ formCssClass: 'dynamic-form', // CSS class applied to each form in a formset\r
+ extraClasses: [], // Additional CSS classes, which will be applied to each form in turn\r
+ added: null, // Function called each time a new form is added\r
+ removed: null // Function called each time a form is deleted\r
+ };\r
+})(jQuery)\r
--- /dev/null
+-- Un chercheur a plusieurs expertises et non le contraire
+
+ALTER TABLE chercheurs_expertise ADD COLUMN `chercheur_id` integer NOT NULL AFTER id;
+
+UPDATE chercheurs_expertise e
+INNER JOIN chercheurs_chercheur c ON c.expertise = e.id
+SET e.chercheur_id = c.id;
+
+ALTER TABLE chercheurs_chercheur DROP COLUMN expertise;
{% if forms.has_errors %}
<span class="message">Votre fiche n'a pas été enregistrée. Veuillez remplir tous les champs obligatoires (*).</span>
{% endif %}
- <fieldset class="horizontal-radio-buttons">
- <legend>Informations personnelles</legend>
- {% with forms.personne as form %}
- {% include "table_form.html" %}
- {% endwith %}
- </fieldset>
+<fieldset class="horizontal-radio-buttons">
+ <legend>Informations personnelles</legend>
+ {% with forms.personne as form %}
+ {% include "table_form.html" %}
+ {% endwith %}
+</fieldset>
- <fieldset>
- <legend>Informations académiques</legend>
- <table>
- {% form_field forms.chercheur.statut %}
- {% form_field forms.chercheur.diplome %}
- {% form_field forms.groupes.groupes %}
- </table>
- </fieldset>
+<fieldset>
+ <legend>Informations académiques</legend>
+ <table>
+ {% form_field forms.chercheur.statut %}
+ {% form_field forms.chercheur.diplome %}
+ {% form_field forms.groupes.groupes %}
+ </table>
+</fieldset>
+<fieldset>
+ <legend>Etablissement de rattachement <span style="color:red">*</span></legend>
+ <table>
+ {% form_field forms.chercheur.etablissement %}
+ </table>
+ <p>Si l'établissement n'existe pas ci-dessus</p>
+ <table>
+ {% form_field forms.chercheur.etablissement_autre_nom %}
+ {% form_field forms.chercheur.etablissement_autre_pays %}
+ </table>
+</fieldset>
+
+<fieldset>
+ <legend>Discipline, thèmes de recherche</legend>
+ <table>
+ {% form_field forms.chercheur.discipline %}
+ {% form_field forms.chercheur.theme_recherche %}
+ {% form_field forms.chercheur.groupe_recherche %}
+ {% form_field forms.chercheur.mots_cles %}
+ {% form_field forms.chercheur.url_site_web %}
+ {% form_field forms.chercheur.url_blog %}
+ {% form_field forms.chercheur.url_reseau_social %}
+ </table>
+</fieldset>
+
+<fieldset>
+ <legend>Activités en Francophonie</legend>
+ <table class="horizontal-radio-buttons">
+ {% form_field forms.chercheur.membre_instance_auf %}
+ {% form_field forms.chercheur.membre_instance_auf_dates %}
+ {% form_field forms.chercheur.expert_oif %}
+ {% form_field forms.chercheur.membre_fipf %}
+ {% form_field forms.chercheur.membre_fipf_association %}
+ </table>
+</fieldset>
+
+<fieldset id="expertises" class="horizontal-radio-buttons">
+ <legend>Expertises</legend>
+ {{ forms.expertises.management_form }}
+ {% for form in forms.expertises.forms %}
<fieldset>
- <legend>Etablissement de rattachement <span style="color:red">*</span></legend>
- <table>
- {% form_field forms.chercheur.etablissement %}
- </table>
- <p>Si l'établissement n'existe pas ci-dessus</p>
- <table>
- {% form_field forms.chercheur.etablissement_autre_nom %}
- {% form_field forms.chercheur.etablissement_autre_pays %}
- </table>
- </fieldset>
-
- <fieldset>
- <legend>Discipline, thèmes de recherche</legend>
<table>
- {% form_field forms.chercheur.discipline %}
- {% form_field forms.chercheur.theme_recherche %}
- {% form_field forms.chercheur.groupe_recherche %}
- {% form_field forms.chercheur.mots_cles %}
- {% form_field forms.chercheur.url_site_web %}
- {% form_field forms.chercheur.url_blog %}
- {% form_field forms.chercheur.url_reseau_social %}
- </table>
- </fieldset>
-
- <fieldset>
- <legend>Activités en Francophonie</legend>
- <table class="horizontal-radio-buttons">
- {% form_field forms.chercheur.membre_instance_auf %}
- {% form_field forms.chercheur.membre_instance_auf_dates %}
- {% form_field forms.chercheur.expert_oif %}
- {% form_field forms.chercheur.membre_fipf %}
- {% form_field forms.chercheur.membre_fipf_association %}
+ {% form_field form.nom %}
+ {% form_field form.date %}
+ {% form_field form.organisme_demandeur %}
+ {% form_field form.organisme_demandeur_visible %}
</table>
+ {{ form.id }}
+ {{ form.DELETE }}
</fieldset>
+ {% endfor %}
+</fieldset>
- <fieldset class="horizontal-radio-buttons">
- <legend>Expertise</legend>
- <div>
- {% with forms.expertise as form %}
- {% include "table_form.html" %}
+<fieldset>
+ <legend>Thèse ou mémoire</legend>
+ <div>
+ <div class="publication">
+ {% with forms.these as form %}
+ {% include "table_form.html" %}
{% endwith %}
</div>
- </fieldset>
-
- <fieldset>
- <legend>Thèse ou mémoire</legend>
- <div>
- <div class="publication">
- {% with forms.these as form %}
- {% include "table_form.html" %}
- {% endwith %}
- </div>
- <div style="clear:both"></div>
- </div>
- </fieldset>
+ <div style="clear:both"></div>
+ </div>
+</fieldset>
{% extends "container_base.html" %}
+{% block extra-script %}
+<script type="text/javascript" src="{{ MEDIA_URL }}js/jquery.formset.js"></script>
+<script type="text/javascript" src="{{ MEDIA_URL }}js/chercheur_edit.js"></script>
+{% endblock %}
+
{% block contenu %}
<h4>{{chercheur.personne.prenom}} {{chercheur.personne.nom}}</h4>
<div class="contenu-wrapper">
</table>
{% endif %}
- {% if chercheur.expertise %}
- <h5>Expertise</h5>
+ {% if chercheur.expertises.all %}
+ <h5>Expertises</h5>
+ {% for expertise in chercheur.expertises.all %}
<table>
<tr>
<td class="label">Titre de l'expertise:</td>
- <td>{{ chercheur.expertise.nom }}</td>
+ <td>{{ expertise.nom }}</td>
</tr>
- {% if chercheur.expertise.date %}
+ {% if expertise.date %}
<tr>
<td class="label">Date:</td>
- <td>{{ chercheur.expertise.date }}</td>
+ <td>{{ expertise.date }}</td>
</tr>
{% endif %}
- {% if chercheur.expertise.organisme_demandeur and chercheur.expertise.organisme_demandeur_visible %}
+ {% if expertise.organisme_demandeur and expertise.organisme_demandeur_visible %}
<tr>
<td class="label">Organisme demandeur:</td>
- <td>{{ chercheur.expertise.organisme_demandeur }}</td>
+ <td>{{ expertise.organisme_demandeur }}</td>
</tr>
{% endif %}
</table>
+ {% if not forloop.last %}
+ <hr>
+ {% endif %}
+ {% endfor %}
{% endif %}
<h5>Publications</h5>
{% extends "container_base.html" %}
+{% block extra-script %}
+<script type="text/javascript" src="{{ MEDIA_URL }}js/jquery.formset.js"></script>
+<script type="text/javascript" src="{{ MEDIA_URL }}js/chercheur_edit.js"></script>
+{% endblock %}
+
{% block contenu %}
<h4>Inscription au répertoire de chercheurs</h4>