From: Guillaume Pellerin Date: Tue, 6 Sep 2016 20:35:22 +0000 (+0200) Subject: Add ProjectProgram and ProjectProgramType, add test version of IRCAM's person data X-Git-Url: https://git.parisson.com/?a=commitdiff_plain;h=74eb25bc401d9b96fd8da5d0250237f72139d92b;p=mezzo.git Add ProjectProgram and ProjectProgramType, add test version of IRCAM's person data --- diff --git a/app/organization/magazine/models.py b/app/organization/magazine/models.py index 647c47ef..d3de1d8e 100644 --- a/app/organization/magazine/models.py +++ b/app/organization/magazine/models.py @@ -16,8 +16,7 @@ from organization.core.models import * class Article(BlogPost, SubTitled): - related_articles = models.ManyToManyField("self", - verbose_name=_("Related articles"), blank=True) + related_articles = models.ManyToManyField("self", verbose_name=_("Related articles"), blank=True) department = models.ForeignKey(Department, verbose_name=_('department'), related_name='articles', limit_choices_to=dict(id__in=Department.objects.all()), blank=True, null=True, on_delete=models.SET_NULL) topics = models.ManyToManyField("Topic", verbose_name=_('topics'), related_name="articles", blank=True) diff --git a/app/organization/network/management/commands/import-ircam-person-xls.py b/app/organization/network/management/commands/import-ircam-person-xls.py new file mode 100644 index 00000000..86c9606b --- /dev/null +++ b/app/organization/network/management/commands/import-ircam-person-xls.py @@ -0,0 +1,155 @@ +import os +import sys +import csv +import logging +import datetime +from optparse import make_option +import xlrd +from itertools import takewhile + +from django.conf import settings +from django.core.management.base import BaseCommand, CommandError +from django.contrib.auth.models import User + +from organization.core.models import * +from organization.network.models import * +from organization.projects.models import * + + +class Logger: + + def __init__(self, file): + self.logger = logging.getLogger('myapp') + self.hdlr = logging.FileHandler(file) + self.formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') + self.hdlr.setFormatter(self.formatter) + self.logger.addHandler(self.hdlr) + self.logger.setLevel(logging.INFO) + + def info(self, prefix, message): + self.logger.info(' ' + prefix + ' : ' + message.decode('utf8')) + + def error(self, prefix, message): + self.logger.error(prefix + ' : ' + message.decode('utf8')) + + +def get_instance(model, field, value): + models = model.objects.filter(field=value) + if models: + return models[0] + else: + model = model() + model.field = value + return model + + +class IrcamXLS: + + sheet_id = 2 + first_row = 21 + + def __init__(self, file): + self.book = xlrd.open_workbook(file) + self.sheet = self.book.sheet_by_index(self.sheet_id) + self.size = self.column_len(0) + + def column_len(self, index): + col_values = self.sheet.col_values(index) + col_len = len(col_values) + for _ in takewhile(lambda x: not x, reversed(col_values)): + col_len -= 1 + return col_len + +class IrcamPerson(object): + + organization = Organization.objects.get(name='Ircam') + + def __init__(self, row, datemode): + self.row = row + self.datemode = datemode + last_name = self.row[0].value + first_name = self.row[1].value + title = ' '.join((first_name, last_name)) + + self.person, c = Person.objects.get_or_create(title=title, first_name=first_name, last_name=last_name) + self.activity = PersonActivity(person=self.person) + + def get_identity(self, ): + gender = self.row[2].value + if gender == 'H': + self.person.gender = 'male' + elif gender == 'F': + self.person.gender = 'female' + + birthday = self.row[3].value + if birthday: + self.person.birthday = datetime.datetime(*xlrd.xldate_as_tuple(birthday, self.datemode)) + + self.person.save() + + def get_or_create_name(self, model, column_id): + return model.objects.get_or_create(name=self.row[column_id].value)[0] if self.row[column_id].value else None + + def get_activity(self): + self.activity.status = self.get_or_create_name(ActivityStatus, 10) + self.activity.is_permanent = True if self.row[11].value else False + self.activity.framework = self.get_or_create_name(ActivityFramework, 12) + self.activity.grade = self.get_or_create_name(ActivityGrade, 13) + + self.activity.employer = self.get_or_create_name(Organization, 14) + self.activity.attachment_organization = self.get_or_create_name(Organization, 15) + self.activity.second_employer = self.get_or_create_name(Organization, 16) + self.activity.umr = self.get_or_create_name(UMR, 17) + + self.activity.team, c = Team.objects.get_or_create(name=self.row[18].value, organization=self.organization) if self.row[18].value else (None, False) + self.activity.second_team, c = Team.objects.get_or_create(name=self.row[19].value, organization=self.organization) if self.row[19].value else (None, False) + self.activity.project, c = Project.objects.get_or_create(title=self.row[19].value) if self.row[19].value else (None, False) + + quota = self.row[21].value + try: + self.activity.rd_quota_float = float(quota) + except: + self.activity.rd_quota_text = str(quota) + + self.activity.phd_doctoral_school = self.get_or_create_name(Organization, 23) + self.activity.phd_director, c = Person.objects.get_or_create(title=self.row[24].value.capitalize()) if self.row[24].value else (None, False) + self.activity.phd_officer_1, c = Person.objects.get_or_create(title=self.row[25].value.capitalize()) if self.row[25].value else (None, False) + self.activity.phd_officer_2, c = Person.objects.get_or_create(title=self.row[26].value.capitalize()) if self.row[26].value else (None, False) + # self.activity.phd_defense_date = datetime.datetime(*xlrd.xldate_as_tuple(self.row[27].value, self.datemode)) if self.row[27].value else None + # self.activity.phd_title = self.row[28].value + + self.activity.save() + + +class Command(BaseCommand): + help = """Import Person data from IRCAM's legacy XLS management file""" + + option_list = BaseCommand.option_list + ( + make_option('-d', '--dry-run', + action='store_true', + dest='dry-run', + help='Do NOT write anything'), + make_option('-f', '--force', + action='store_true', + dest='force', + help='Force overwrite data'), + make_option('-s', '--source', + dest='source_file', + help='define the XLS source file'), + make_option('-l', '--log', + dest='log', + help='define log file'), + ) + + def handle(self, *args, **kwargs): + # self.logger = Logger(kwargs.get('log')) + self.pattern = kwargs.get('pattern') + self.source_file = os.path.abspath(kwargs.get('source_file')) + self.dry_run = kwargs.get('dry-run') + self.force = kwargs.get('force') + + xls = IrcamXLS(self.source_file) + for i in range(xls.first_row, xls.size): + p = IrcamPerson(xls.sheet.row(i), xls.book.datemode) + p.get_identity() + p.get_activity() diff --git a/app/organization/network/migrations/0005_auto_20160905_1853.py b/app/organization/network/migrations/0005_auto_20160905_1853.py new file mode 100644 index 00000000..3055818c --- /dev/null +++ b/app/organization/network/migrations/0005_auto_20160905_1853.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-09-05 16:53 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('organization-network', '0004_organizationaudio_organizationblock_organizationimage_organizationlink_organizationvideo'), + ] + + operations = [ + migrations.CreateModel( + name='UMR', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=512, verbose_name='name')), + ('description', models.TextField(blank=True, verbose_name='description')), + ], + options={ + 'verbose_name': 'UMR', + }, + ), + migrations.RemoveField( + model_name='person', + name='permanent', + ), + migrations.RemoveField( + model_name='personactivity', + name='function', + ), + migrations.RemoveField( + model_name='personactivity', + name='rd_quota', + ), + migrations.AddField( + model_name='personactivity', + name='is_permanent', + field=models.BooleanField(default=False, verbose_name='permanent'), + ), + migrations.AddField( + model_name='personactivity', + name='rd_quota_float', + field=models.IntegerField(blank=True, null=True, verbose_name='R&D quota (float)'), + ), + migrations.AddField( + model_name='personactivity', + name='rd_quota_text', + field=models.CharField(blank=True, max_length=128, null=True, verbose_name='R&D quota (text)'), + ), + migrations.AlterField( + model_name='person', + name='user', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='user'), + ), + migrations.AddField( + model_name='personactivity', + name='umr', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='organization-network.UMR', verbose_name='training type'), + ), + ] diff --git a/app/organization/network/models.py b/app/organization/network/models.py index 9f7e610d..70e5923f 100644 --- a/app/organization/network/models.py +++ b/app/organization/network/models.py @@ -163,14 +163,13 @@ class TeamPage(Page, SubTitled, RichText): class Person(Displayable, AdminThumbMixin): """(Person description)""" - user = models.ForeignKey(User, verbose_name=_('user'), blank=True, null=True, on_delete=models.SET_NULL) + user = models.OneToOneField(User, verbose_name=_('user'), blank=True, null=True, on_delete=models.SET_NULL) person_title = models.CharField(_('title'), max_length=16, choices=TITLE_CHOICES, blank=True) gender = models.CharField(_('gender'), max_length=16, choices=GENDER_CHOICES, blank=True) first_name = models.CharField(_('first name'), max_length=255, blank=True, null=True) last_name = models.CharField(_('last name'), max_length=255, blank=True, null=True) birthday = models.DateField(_('birthday'), blank=True, null=True) bio = RichTextField(_('biography'), blank=True) - permanent = models.BooleanField(_('permanent'), default=False) class Meta: verbose_name = _('person') @@ -282,25 +281,32 @@ class TrainingSpectiality(Named): verbose_name = _('training speciality') +class UMR(Named): + + class Meta: + verbose_name = _('UMR') + + class PersonActivity(Description, Period, RichText): """(Activity description)""" person = models.ForeignKey('Person', verbose_name=_('person')) - team = models.ForeignKey('Team', verbose_name=_('team'), related_name='team_activity', blank=True, null=True, on_delete=models.SET_NULL) - second_team = models.ForeignKey('Team', verbose_name=_('second team'), related_name='second_team_activity', blank=True, null=True, on_delete=models.SET_NULL) - function = models.CharField(_('fonction'), blank=True, max_length=1024) status = models.ForeignKey(ActivityStatus, verbose_name=_('status'), blank=True, null=True, on_delete=models.SET_NULL) - grade = models.ForeignKey(ActivityGrade, verbose_name=_('grade'), blank=True, null=True, on_delete=models.SET_NULL) + is_permanent = models.BooleanField(_('permanent'), default=False) framework = models.ForeignKey(ActivityFramework, verbose_name=_('framework'), blank=True, null=True, on_delete=models.SET_NULL) - hdr = models.BooleanField(_('HDR'), default=False) + grade = models.ForeignKey(ActivityGrade, verbose_name=_('grade'), blank=True, null=True, on_delete=models.SET_NULL) employer = models.ForeignKey(Organization, verbose_name=_('employer'), related_name='employer_activity', blank=True, null=True, on_delete=models.SET_NULL) - second_employer = models.ForeignKey(Organization, verbose_name=_('second employer'), related_name='second_employer_activity', blank=True, null=True, on_delete=models.SET_NULL) attachment_organization = models.ForeignKey(Organization, verbose_name=_('attachment organization'), related_name='attachment_activity', blank=True, null=True, on_delete=models.SET_NULL) + second_employer = models.ForeignKey(Organization, verbose_name=_('second employer'), related_name='second_employer_activity', blank=True, null=True, on_delete=models.SET_NULL) + umr = models.ForeignKey(UMR, verbose_name=_('training type'), blank=True, null=True, on_delete=models.SET_NULL) + team = models.ForeignKey('Team', verbose_name=_('team'), related_name='team_activity', blank=True, null=True, on_delete=models.SET_NULL) + second_team = models.ForeignKey('Team', verbose_name=_('second team'), related_name='second_team_activity', blank=True, null=True, on_delete=models.SET_NULL) project = models.ForeignKey('organization-projects.Project', verbose_name=_('project'), blank=True, null=True, on_delete=models.SET_NULL) - rd_quota = models.IntegerField(_('R&D quota'), blank=True, null=True) + rd_quota_float = models.IntegerField(_('R&D quota (float)'), blank=True, null=True) + rd_quota_text = models.CharField(_('R&D quota (text)'), blank=True, null=True, max_length=128) rd_program = models.TextField(_('R&D program'), blank=True) budget_code = models.ForeignKey(BudgetCode, blank=True, null=True, on_delete=models.SET_NULL) @@ -311,6 +317,7 @@ class PersonActivity(Description, Period, RichText): phd_defense_date = models.DateField(_('PhD defense date'), blank=True, null=True) phd_title = models.TextField(_('PhD title'), blank=True) phd_postdoctoralsituation = models.CharField(_('post-doctoral situation'), blank=True, max_length=256) + hdr = models.BooleanField(_('HDR'), default=False) training_type = models.ForeignKey(TrainingType, verbose_name=_('training type'), blank=True, null=True, on_delete=models.SET_NULL) training_level = models.ForeignKey(TrainingLevel, verbose_name=_('training level'), blank=True, null=True, on_delete=models.SET_NULL) @@ -318,13 +325,13 @@ class PersonActivity(Description, Period, RichText): training_speciality = models.ForeignKey(TrainingSpectiality, verbose_name=_('training speciality'), blank=True, null=True, on_delete=models.SET_NULL) training_title = models.TextField(_('Training title'), blank=True) - comments = models.TextField(_('comments'), blank=True) - record_piece = models.ForeignKey(RecordPiece, blank=True, null=True, on_delete=models.SET_NULL) date_added = models.DateTimeField(_('add date'), auto_now_add=True) date_modified = models.DateTimeField(_('modification date'), auto_now=True) date_modified_manual = models.DateTimeField(_('manual modification date'), blank=True, null=True) + comments = models.TextField(_('comments'), blank=True) + class Meta: verbose_name = _('activity') verbose_name_plural = _('activities') diff --git a/app/organization/projects/admin.py b/app/organization/projects/admin.py index e79d505d..ada735ba 100644 --- a/app/organization/projects/admin.py +++ b/app/organization/projects/admin.py @@ -45,6 +45,9 @@ class ProjectAdminDisplayable(DisplayableAdmin): fieldsets = deepcopy(ProjectAdmin.fieldsets) inlines = [ ProjectBlockInline, ProjectImageInline, ProjectAudioInline, ProjectVideoInline, ProjectLinkInline] filter_horizontal = ['persons', 'teams', 'organizations'] + list_filter = ['type', 'program', 'program_type', ] admin.site.register(Project, ProjectAdminDisplayable) +admin.site.register(ProjectProgram) +admin.site.register(ProjectProgramType) diff --git a/app/organization/projects/migrations/0004_auto_20160905_1853.py b/app/organization/projects/migrations/0004_auto_20160905_1853.py new file mode 100644 index 00000000..c8f8044f --- /dev/null +++ b/app/organization/projects/migrations/0004_auto_20160905_1853.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.7 on 2016-09-05 16:53 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('organization-projects', '0003_auto_20160901_1810'), + ] + + operations = [ + migrations.CreateModel( + name='ProjectProgram', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=512, verbose_name='name')), + ('description', models.TextField(blank=True, verbose_name='description')), + ], + options={ + 'verbose_name': 'project programme', + }, + ), + migrations.CreateModel( + name='ProjectProgramType', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=512, verbose_name='name')), + ('description', models.TextField(blank=True, verbose_name='description')), + ], + options={ + 'verbose_name': 'project programme type', + }, + ), + migrations.AddField( + model_name='project', + name='type', + field=models.CharField(choices=[('research topic', 'research topic'), ('collaborative project', 'collaborative project')], default=1, max_length=128, verbose_name='type'), + preserve_default=False, + ), + migrations.AlterField( + model_name='project', + name='teams', + field=models.ManyToManyField(blank=True, related_name='partner_projects', to='organization-network.Team', verbose_name='teams'), + ), + migrations.AddField( + model_name='project', + name='program', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='projects', to='organization-projects.ProjectProgram', verbose_name='project program'), + ), + migrations.AddField( + model_name='project', + name='program_type', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='projects', to='organization-projects.ProjectProgramType', verbose_name='project program type'), + ), + ] diff --git a/app/organization/projects/models.py b/app/organization/projects/models.py index 4ee2c20b..cf658b06 100644 --- a/app/organization/projects/models.py +++ b/app/organization/projects/models.py @@ -9,12 +9,20 @@ from organization.core.models import * from organization.pages.models import * +PROJECT_TYPE_CHOICES = [ + ('internal project', _('internal project')), + ('external project', _('external project')), +] + class Project(Displayable, Period, RichText): """(Project description)""" + type = models.CharField(_('type'), max_length=128, choices=PROJECT_TYPE_CHOICES) + program = models.ForeignKey('ProjectProgram', verbose_name=_('project program'), related_name='projects', blank=True, null=True, on_delete=models.SET_NULL) + program_type = models.ForeignKey('ProjectProgramType', verbose_name=_('project program type'), related_name='projects', blank=True, null=True, on_delete=models.SET_NULL) lead_team = models.ForeignKey('organization-network.Team', verbose_name=_('lead team'), related_name='leader_projects', blank=True, null=True) persons = models.ManyToManyField('organization-network.Person', verbose_name=_('persons'), blank=True) - teams = models.ManyToManyField('organization-network.Team', verbose_name=_('teams'), related_name='patner_projects', blank=True) + teams = models.ManyToManyField('organization-network.Team', verbose_name=_('teams'), related_name='partner_projects', blank=True) organizations = models.ManyToManyField('organization-network.Organization', verbose_name=_('organizations'), blank=True) website = models.URLField(_('website'), max_length=512, blank=True) @@ -28,6 +36,18 @@ class Project(Displayable, Period, RichText): return reverse("organization-project-detail", kwargs={"slug": self.slug}) +class ProjectProgram(Named): + + class Meta: + verbose_name = _('project programme') + + +class ProjectProgramType(Named): + + class Meta: + verbose_name = _('project programme type') + + class ProjectAudio(Audio): project = models.ForeignKey(Project, verbose_name=_('project'), related_name='audios', blank=True, null=True, on_delete=models.SET_NULL)