Пример #1
0
class Migration(migrations.Migration):
    dependencies = [
        ('tests', '0002_add_tmp_model'),
    ]

    operations = [
        migrations.AddField('Something', 'path', PathField(order_by=('name',), max_siblings=ALPHANUM_LEN)),
        CreateTreeTrigger('Something'),
        RebuildPaths('Something'),
        migrations.AlterField('Something', 'path', PathField(order_by=('name',), max_siblings=ALPHANUM_LEN*3)),
        DeleteTreeTrigger('Something'),
        migrations.DeleteModel('Something'),
    ]
Пример #2
0
class Migration(migrations.Migration):
    dependencies = [
        ('tests', '0002_add_tmp_model'),
    ]

    operations = [
        migrations.AddField('Something', 'path', PathField(null=True)),
        CreateTreeTrigger('Something', order_by=('name',)),
        CreateTreeIndex('Something'),
        RebuildPaths('Something'),
        migrations.AlterField('Something', 'path', PathField()),
        DeleteTreeIndex('Something'),
        DeleteTreeTrigger('Something', order_by=('name',)),
        migrations.DeleteModel('Something'),
    ]
Пример #3
0
class Place(Model, TreeModelMixin):
    name = CharField(max_length=50)
    parent = ForeignKey('self', null=True, blank=True)
    path = PathField()

    class Meta:
        ordering = ('path', 'name')
Пример #4
0
class Migration(migrations.Migration):
    dependencies = [
        ('tree', '0001_initial'),
    ]

    operations = [
        migrations.CreateModel(
            name='Place',
            fields=[
                ('id',
                 models.AutoField(verbose_name='ID',
                                  serialize=False,
                                  auto_created=True,
                                  primary_key=True)),
                ('name', models.CharField(max_length=50)),
                ('parent',
                 models.ForeignKey('self',
                                   blank=True,
                                   null=True,
                                   on_delete=CASCADE)),
                ('path',
                 PathField(order_by=('name', ),
                           max_siblings=ALPHANUM_LEN * 3)),
            ],
            options={
                'ordering': ('path', 'name'),
            },
        ),
        CreateTreeTrigger('tests.Place'),
    ]
Пример #5
0
class Place(TreeModel):
    name = CharField(max_length=50)
    parent = ForeignKey('self', null=True, blank=True, on_delete=CASCADE)
    path = PathField(order_by=('name', ), max_siblings=ALPHANUM_LEN * 3)

    class Meta:
        ordering = ('path', 'name')
Пример #6
0
class TreePlace(TreeModel):
    name = CharField(max_length=50, unique=True, default=get_random_name)
    parent = ForeignKey('self',
                        null=True,
                        blank=True,
                        related_name='children',
                        on_delete=CASCADE)
    path = PathField(order_by=('name', ), db_index=True)
Пример #7
0
class Space(models.Model):
    id = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False)
    name = models.CharField(max_length=256, null=False)
    parent = models.ForeignKey('self',
                               null=True,
                               blank=True,
                               on_delete=models.SET_NULL)
    path = PathField()

    class Meta:
        ordering = ('path', )
Пример #8
0
class DossierDEvenements(TreeModelMixin, PublishedModel):
    categorie = ForeignKey(
        CategorieDeDossiers, null=True, blank=True,
        related_name='dossiersdevenements', verbose_name=_('catégorie'),
        help_text=_('Attention, un dossier contenu dans un autre dossier '
                    'ne peut être dans une catégorie.'), on_delete=CASCADE)
    titre = CharField(_('titre'), max_length=100)
    titre_court = CharField(_('titre court'), max_length=100, blank=True,
                            help_text=_('Utilisé pour le chemin de fer.'))
    # TODO: Ajouter accroche d'environ 150 caractères.
    parent = ForeignKey('self', null=True, blank=True,
                        related_name='children', verbose_name=_('parent'),
                        on_delete=CASCADE)
    path = PathField(order_by=('position',), db_index=True)
    position = PositiveSmallIntegerField(_('position'), default=1)
    slug = SlugField(unique=True,
                     help_text=_('Personnaliser l’affichage du titre '
                                 'du dossier dans l’adresse URL.'))

    # Métadonnées
    editeurs_scientifiques = ManyToManyField(
        'accounts.HierarchicUser', related_name='dossiers_d_evenements_edites',
        verbose_name=_('éditeurs scientifiques'))
    date_publication = DateField(_('date de publication'),
                                 default=datetime.now)
    publications = TextField(_('publication(s) associée(s)'), blank=True)
    developpements = TextField(_('développements envisagés'), blank=True)

    # Article
    presentation = TextField(_('présentation'))
    contexte = TextField(_('contexte historique'), blank=True)
    sources_et_protocole = TextField(_('sources et protocole'), blank=True)
    bibliographie = TextField(_('bibliographie indicative'), blank=True)

    # Sélecteurs
    debut = DateField(_('début'), blank=True, null=True)
    fin = DateField(_('fin'), blank=True, null=True)
    lieux = ManyToManyField(Lieu, blank=True, verbose_name=_('lieux'),
                            related_name='dossiers')
    oeuvres = ManyToManyField(Oeuvre, blank=True, verbose_name=_('œuvres'),
                              related_name='dossiers')
    auteurs = ManyToManyField(Individu, blank=True, verbose_name=_('auteurs'),
                              related_name='dossiers')
    circonstance = CharField(_('circonstance'), max_length=100, blank=True)
    evenements = ManyToManyField(Evenement, verbose_name=_('événements'),
                                 blank=True, related_name='dossiers')
    ensembles = ManyToManyField(Ensemble, verbose_name=_('ensembles'),
                                blank=True, related_name='dossiers')
    sources = ManyToManyField(Source, verbose_name=_('sources'), blank=True,
                              related_name='dossiers')
    saisons = ManyToManyField(Saison, verbose_name=_('saisons'), blank=True,
                              related_name=_('saisons'))

    objects = DossierDEvenementsManager()

    class Meta(object):
        verbose_name = _('dossier d’événements')
        verbose_name_plural = _('dossiers d’événements')
        ordering = ('path',)
        permissions = (('can_change_status', _('Peut changer l’état')),)

    def __str__(self):
        return strip_tags(self.html())

    def html(self):
        return mark_safe(self.titre)

    def link(self):
        return href(self.get_absolute_url(), smart_text(self))

    def short_link(self):
        return href(self.get_absolute_url(), self.titre_court or self.titre)

    @permalink
    def get_absolute_url(self):
        return 'dossierdevenements_detail', (self.slug,)

    @permalink
    def permalien(self):
        return 'dossierdevenements_permanent_detail', (self.pk,)

    @permalink
    def get_data_absolute_url(self):
        return 'dossierdevenements_data_detail', (self.slug,)

    def get_queryset(self, dynamic=False):
        if hasattr(self, '_evenement_queryset'):
            return self._evenement_queryset
        if not dynamic and self.pk and self.evenements.exists():
            return self.evenements.all()
        kwargs = {}
        if self.debut:
            kwargs['debut_date__gte'] = self.debut
        if self.fin:
            kwargs['debut_date__lte'] = self.fin
        if self.pk:
            if self.lieux.exists():
                lieux = self.lieux.all().get_descendants(include_self=True)
                kwargs['debut_lieu__in'] = lieux
            if self.oeuvres.exists():
                oeuvres = self.oeuvres.all().get_descendants(include_self=True)
                kwargs['programme__oeuvre__in'] = oeuvres
            auteurs = self.auteurs.all()
            if auteurs.exists():
                kwargs['programme__oeuvre__auteurs__individu__in'] = auteurs
            if self.ensembles.exists():
                evenements = Evenement.objects.extra(where=("""
                id IN (
                    SELECT DISTINCT COALESCE(distribution.evenement_id, programme.evenement_id)
                    FROM dossiers_dossierdevenements_ensembles AS dossier_ensemble
                    INNER JOIN libretto_elementdedistribution AS distribution
                        ON (distribution.ensemble_id = dossier_ensemble.ensemble_id)
                    LEFT JOIN libretto_elementdeprogramme AS programme
                        ON (programme.id = distribution.element_de_programme_id)
                    WHERE dossier_ensemble.dossierdevenements_id = %s
                )""",), params=(self.pk,))
                kwargs['pk__in'] = evenements
            sources = self.sources.all()
            if sources.exists():
                kwargs['sources__in'] = sources
            saisons = self.saisons.all()
            if saisons.exists():
                kwargs['pk__in'] = saisons.evenements()
        if self.circonstance:
            kwargs['circonstance__icontains'] = self.circonstance
        if kwargs:
            self._evenement_queryset = Evenement.objects.filter(
                **kwargs).distinct()
        else:
            self._evenement_queryset = Evenement.objects.none()
        return self._evenement_queryset
    get_queryset.short_description = _('ensemble de données')

    def get_count(self):
        return self.get_queryset().count()
    get_count.short_description = _('quantité de données sélectionnées')

    def get_queryset_url(self):
        url = reverse('evenements')
        request_kwargs = []
        if self.lieux.exists():
            request_kwargs.append('lieu=|%s|' % '|'.join([str(l.pk)
                                  for l in self.lieux.all()]))
        if self.oeuvres.exists():
            request_kwargs.append('oeuvre=|%s|' % '|'.join([str(o.pk)
                                  for o in self.oeuvres.all()]))
        if request_kwargs:
            url += '?' + '&'.join(request_kwargs)
        return url

    def lieux_html(self):
        return str_list_w_last([l.html() for l in self.lieux.all()])
    lieux_html.short_description = _('lieux')
    lieux_html.allow_tags = True

    def oeuvres_html(self):
        return str_list_w_last([o.titre_html() for o in self.oeuvres.all()])
    oeuvres_html.short_description = _('œuvres')
    oeuvres_html.allow_tags = True

    def auteurs_html(self):
        return str_list_w_last([a.html() for a in self.auteurs.all()])
    auteurs_html.short_description = _('auteurs')
    auteurs_html.allow_tags = True

    def ensembles_html(self):
        return str_list_w_last([e.html() for e in self.ensembles.all()])
    ensembles_html.short_description = _('ensembles')
    ensembles_html.allow_tags = True

    def get_contributors(self):
        return HierarchicUser.objects.filter(
            Q(pk__in=self.get_queryset().values_list('owner_id',
                                                     flat=True).distinct()) |
            Q(pk__in=self.get_queryset().values_list('sources__owner_id',
                                                     flat=True).distinct())
        ).order_by('last_name', 'first_name')
Пример #9
0
class Lieu(TreeModelMixin, AutoriteModel, UniqueSlugModel):
    nom = CharField(_('nom'), max_length=200, db_index=True)
    parent = ForeignKey('self',
                        null=True,
                        blank=True,
                        related_name='enfants',
                        verbose_name=_('parent'),
                        on_delete=CASCADE)
    path = PathField(order_by=('nom', ), db_index=True)
    nature = ForeignKey(NatureDeLieu,
                        related_name='lieux',
                        verbose_name=_('nature'),
                        on_delete=PROTECT)
    is_institution = BooleanField(_('institution'), default=False)
    # TODO: Parentés d'institution avec périodes d'activité pour l'histoire des
    # institutions.
    historique = HTMLField(_('historique'), blank=True)
    geometry = GeometryField(_('géo-positionnement'),
                             blank=True,
                             null=True,
                             db_index=True)

    objects = LieuManager()

    class Meta(object):
        verbose_name = _('lieu ou institution')
        verbose_name_plural = _('lieux et institutions')
        ordering = ('path', )
        unique_together = (
            'nom',
            'parent',
        )
        permissions = (('can_change_status', _('Peut changer l’état')), )

    @staticmethod
    def invalidated_relations_when_saved(all_relations=False):
        relations = (
            'enfants',
            'evenement_debut_set',
            'evenement_fin_set',
        )
        if all_relations:
            relations += (
                'individu_naissance_set',
                'individu_deces_set',
                'oeuvre_creation_set',
                'dossiers',
            )
        return relations

    @permalink
    def get_absolute_url(self):
        return 'lieu_detail', [self.slug]

    @permalink
    def permalien(self):
        return 'lieu_permanent_detail', [self.pk]

    def link(self):
        return self.html()

    link.short_description = _('lien')
    link.allow_tags = True

    def get_slug(self):
        parent = super(Lieu, self).get_slug()
        return slugify_unicode(self.nom) or parent

    def short_link(self):
        return self.html(short=True)

    def evenements(self):
        qs = self.get_descendants(include_self=True)
        return Evenement.objects.filter(
            Q(debut_lieu__in=qs) | Q(fin_lieu__in=qs))

    def individus_nes(self):
        return Individu.objects.filter(naissance_lieu__in=self.get_descendants(
            include_self=True)).order_by(*Individu._meta.ordering)

    def individus_decedes(self):
        return Individu.objects.filter(deces_lieu__in=self.get_descendants(
            include_self=True)).order_by(*Individu._meta.ordering)

    def oeuvres_creees(self):
        return Oeuvre.objects.filter(creation_lieu__in=self.get_descendants(
            include_self=True)).order_by(*Oeuvre._meta.ordering)

    def ancestors_until_referent(self):
        l = []
        parent = self
        while parent is not None:
            l.append(parent.nom)
            if parent.nature.referent:
                break
            parent = parent.parent
        return l[::-1]

    def html(self, tags=True, short=False):
        if short or self.parent_id is None or self.nature.referent:
            out = self.nom
        else:
            out = ', '.join(self.ancestors_until_referent())

        url = None if not tags else self.get_absolute_url()
        return href(url, out, tags)

    html.short_description = _('rendu HTML')
    html.allow_tags = True

    def clean(self):
        if self.parent == self:
            raise ValidationError(_('Le lieu a une parenté avec lui-même.'))

    def __str__(self):
        return strip_tags(self.html(False))

    @staticmethod
    def autocomplete_search_fields():
        return ('nom__unaccent__icontains', 'parent__nom__unaccent__icontains')
Пример #10
0
class HierarchicUser(TreeModelMixin, AbstractUser):
    show_email = BooleanField(_('afficher l’email'), default=False)
    website = URLField(_('site internet'), blank=True)
    website_verbose = CharField(
        _('nom affiché du site internet'), max_length=50, blank=True)

    legal_person = BooleanField(
        _('personne morale'), default=False,
        help_text=_('Cochez si vous êtes une institution ou un ensemble.'))
    content_type = ForeignKey(
        ContentType, blank=True, null=True,
        limit_choices_to={'model__in': _get_valid_modelnames_func()},
        verbose_name=_('type d’autorité associée'), on_delete=CASCADE)
    object_id = PositiveIntegerField(_('identifiant de l’autorité associée'),
                                     blank=True, null=True)
    content_object = GenericForeignKey()

    mentor = ForeignKey(
        'self', null=True, blank=True, related_name='disciples',
        verbose_name=_('responsable scientifique'),
        limit_choices_to={'willing_to_be_mentor__exact': True},
        on_delete=CASCADE)
    path = PathField(order_by=('last_name', 'first_name', 'username'),
                     db_index=True)
    willing_to_be_mentor = BooleanField(
        _('Veut être responsable scientifique'), default=False)

    avatar = ImageField(_('photographie d’identité'), upload_to='avatars/',
                        blank=True, null=True)

    presentation = TextField(
        _('présentation'), blank=True, validators=[MaxLengthValidator(5000)])
    fonctions = TextField(_('fonctions au sein de l’équipe'), blank=True,
                          validators=[MaxLengthValidator(200)])
    literature = TextField(_('publications'), blank=True)

    objects = HierarchicUserManager()

    class Meta(object):
        ordering = ('last_name', 'first_name')
        verbose_name = _('utilisateur')
        verbose_name_plural = _('utilisateurs')

    def __str__(self, tags=False):
        return self.html(tags=False)

    def get_full_name(self, tags=False):
        full_name = f'{self.first_name} {sc(self.last_name, tags=tags)}'
        return full_name.strip()

    def html(self, tags=True):
        txt = self.get_full_name(tags=tags) or self.get_username()
        return href(self.get_absolute_url(), txt, tags=tags)

    def link(self, tags=True):
        return self.html(tags=tags)

    @permalink
    def get_absolute_url(self):
        return 'user_profile', (self.username,)

    def website_link(self):
        return href(self.website, self.website_verbose or self.website,
                    new_tab=True)

    def email_link(self):
        return href(f'mailto:{self.email}', self.email)

    def dossiers_edites(self):
        return apps.get_model('dossiers.DossierDEvenements').objects.filter(
            editeurs_scientifiques=self
        ).exclude(parent__editeurs_scientifiques=self)

    @staticmethod
    def autocomplete_search_fields():
        return ('first_name__unaccent__icontains',
                'last_name__unaccent__icontains')
Пример #11
0
class Oeuvre(TreeModelMixin, AutoriteModel, UniqueSlugModel):
    prefixe_titre = CharField(_('article'), max_length=20, blank=True)
    titre = CharField(_('titre'), max_length=200, blank=True, db_index=True)
    coordination = CharField(_('coordination'),
                             max_length=20,
                             blank=True,
                             db_index=True)
    prefixe_titre_secondaire = CharField(_('article'),
                                         max_length=20,
                                         blank=True)
    titre_secondaire = CharField(_('titre secondaire'),
                                 max_length=200,
                                 blank=True,
                                 db_index=True)
    genre = ForeignKey('GenreDOeuvre',
                       related_name='oeuvres',
                       blank=True,
                       null=True,
                       verbose_name=_('genre'),
                       on_delete=PROTECT)
    numero = NumberCharField(
        _('numéro'),
        max_length=10,
        blank=True,
        db_index=True,
        validators=[
            RegexValidator(
                r'^[\d\w\-]+$',
                _('Vous ne pouvez saisir que des chiffres, '
                  'lettres non accentuées et tiret, '
                  'le tout sans espace.'))
        ],
        help_text=_(
            'Exemple : « 5 » pour symphonie n° 5, « 7a » pour valse n° 7 a, '
            'ou encore « 3-7 » pour sonates n° 3 à 7. '
            '<strong>Ne pas confondre avec le sous-numéro d’opus.</strong>'))
    coupe = CharField(
        _('coupe'),
        max_length=100,
        blank=True,
        db_index=True,
        validators=[
            RegexValidator(
                r'^\D+$',
                _('Vous devez saisir les quantités '
                  'en toutes lettres.'))
        ],
        help_text=_('Exemple : « trois actes » pour un opéra en trois actes.'))
    indeterminee = BooleanField(
        _('indéterminée'),
        default=False,
        help_text=_(
            'Cocher si l’œuvre n’est pas identifiable, par exemple '
            'un quatuor de Haydn, sans savoir lequel. '
            '<strong>Ne pas utiliser pour un extrait indéterminé</strong>, '
            'sélectionner plutôt dans le programme l’œuvre dont il est tiré '
            'et joindre une caractéristique le décrivant '
            '(« un air », « un mouvement », etc.).'))
    incipit = CharField(
        _('incipit'),
        max_length=100,
        blank=True,
        db_index=True,
        help_text=_('Exemple : « Belle nuit, ô nuit d’amour » pour le n° 13 '
                    'de l’acte III des <em>Contes d’Hoffmann</em> '
                    'd’Offenbach.'))
    tempo = CharField(
        _('tempo'),
        max_length=50,
        blank=True,
        db_index=True,
        help_text=_('Exemple : « Largo », « Presto ma non troppo », etc. '
                    'Ne pas saisir d’indication métronomique.'))
    NOTES = NOTES
    ALTERATIONS = ALTERATIONS
    GAMMES = GAMMES
    TONALITES = [(f'{gamme_k}{note_k}{alter_k}',
                  _(str_list((note_v, alter_v, gamme_v), ' ')))
                 for gamme_k, gamme_v in GAMMES.items()
                 for note_k, note_v in NOTES.items()
                 for alter_k, alter_v in ALTERATIONS.items()]
    tonalite = CharField(_('tonalité'),
                         max_length=3,
                         choices=TONALITES,
                         blank=True,
                         db_index=True)
    sujet = CharField(
        _('sujet'),
        max_length=80,
        blank=True,
        help_text=_(
            'Exemple : « un thème de Beethoven » pour une variation sur un '
            'thème de Beethoven, « des motifs de '
            '&lt;em&gt;Lucia di Lammermoor&lt;/em&gt; » pour une fantaisie '
            'sur des motifs de <em>Lucia di Lammermoor</em> '
            '(&lt;em&gt; et &lt;/em&gt; sont les balises HTML '
            'pour mettre en emphase).'))
    TRANSCRIPTION = 1
    ORCHESTRATION = 2
    ARRANGEMENTS = ((TRANSCRIPTION, _('transcription')), (ORCHESTRATION,
                                                          _('orchestration')))
    arrangement = PositiveSmallIntegerField(_('arrangement'),
                                            choices=ARRANGEMENTS,
                                            blank=True,
                                            null=True,
                                            db_index=True)
    surnom = CharField(_('surnom'),
                       max_length=50,
                       blank=True,
                       db_index=True,
                       help_text=_(
                           'Exemple : « Jupiter » pour la symphonie n° 41 '
                           'de Mozart.'))
    nom_courant = CharField(
        _('nom courant'),
        max_length=70,
        blank=True,
        db_index=True,
        help_text=_('Exemple : « barcarolle » pour le n° 13 de l’acte III des '
                    '<em>Contes d’Hoffmann</em> d’Offenbach.'))
    opus = CharField(
        _('opus'),
        max_length=6,
        blank=True,
        db_index=True,
        validators=[
            RegexValidator(
                r'^[\d\w\-/]+$',
                _('Vous ne pouvez saisir que des chiffres, '
                  'lettres non accentuées, tiret '
                  'et barre oblique, le tout sans espace.'))
        ],
        help_text=_('Exemple : « 12 » pour op. 12, « 27/3 » pour op. 27 n° 3, '
                    '« 8b » pour op. 8 b, ou encore « 12-15 » '
                    'pour op. 12 à 15.'))
    ict = CharField(_('ICT'),
                    max_length=25,
                    blank=True,
                    db_index=True,
                    help_text=_(
                        'Indice de Catalogue Thématique. Exemple : « RV 42 », '
                        '« K. 299d » ou encore « Hob. XVI:24 ».'))
    CREATION_TYPES = (
        (1, _('genèse (composition, écriture, etc.)')),
        (2, _('première mondiale')),
        (3, _('première édition')),
    )
    creation_type = PositiveSmallIntegerField(_('type de création'),
                                              choices=CREATION_TYPES,
                                              null=True,
                                              blank=True)
    creation = AncrageSpatioTemporel(verbose_name=_('création'))
    ORDERING = ('type_extrait', 'numero_extrait', 'titre', 'genre', 'numero',
                'coupe', 'incipit', 'tempo', 'tonalite', 'sujet',
                'arrangement', 'surnom', 'nom_courant', 'opus', 'ict')
    extrait_de = ForeignKey('self',
                            null=True,
                            blank=True,
                            related_name='enfants',
                            verbose_name=_('extrait de'),
                            on_delete=CASCADE)
    path = PathField(order_by=ORDERING, db_index=True)
    ACTE = 1
    TABLEAU = 2
    SCENE = 3
    MORCEAU = 4
    PARTIE = 5
    LIVRE = 6
    ALBUM = 7
    VOLUME = 8
    CAHIER = 9
    ORDRE = 10
    MOUVEMENT = 11
    PIECE = 12
    SERIE = 13
    TYPES_EXTRAIT_ROMAINS = (ACTE, LIVRE, ORDRE)
    TYPES_EXTRAIT_CACHES = (MORCEAU, MOUVEMENT, PIECE)
    TYPES_EXTRAIT = (
        (ACTE, _('acte')),
        (TABLEAU, _('tableau')),
        (SCENE, _('scène')),
        (MORCEAU, _('morceau chanté')),
        (PARTIE, _('partie')),
        (LIVRE, _('livre')),
        (ALBUM, _('album')),
        (VOLUME, _('volume')),
        (CAHIER, _('cahier')),
        (ORDRE, _('ordre')),
        (MOUVEMENT, _('mouvement')),
        (PIECE, _('pièce de recueil')),
        (SERIE, _('série')),
    )
    type_extrait = PositiveSmallIntegerField(_('type d’extrait'),
                                             choices=TYPES_EXTRAIT,
                                             blank=True,
                                             null=True,
                                             db_index=True)
    NUMERO_EXTRAIT_PATTERN = r'^([1-9]\d*)([^\d\.\-]*)$'
    NUMERO_EXTRAIT_RE = re.compile(NUMERO_EXTRAIT_PATTERN)
    numero_extrait = NumberCharField(
        _('numéro d’extrait'),
        max_length=10,
        blank=True,
        db_index=True,
        help_text=_(
            'Le numéro de l’extrait au sein de l’œuvre, par exemple « 3 » '
            'pour le 3<sup>e</sup> mouvement d’un concerto, « 4 » pour '
            'l’acte IV d’un opéra, ou encore « 12b ».'),
        validators=[
            RegexValidator(
                NUMERO_EXTRAIT_PATTERN,
                _('Vous devez saisir un nombre en chiffres arabes '
                  'éventellement suivi de lettres.'))
        ])
    filles = ManyToManyField('self',
                             through='ParenteDOeuvres',
                             related_name='meres',
                             symmetrical=False,
                             blank=True,
                             verbose_name=_('filles'))

    objects = OeuvreManager()

    class Meta(object):
        verbose_name = _('œuvre')
        verbose_name_plural = _('œuvres')
        ordering = ('path', )
        permissions = (('can_change_status', _('Peut changer l’état')), )

    @staticmethod
    def invalidated_relations_when_saved(all_relations=False):
        relations = (
            'enfants',
            'elements_de_programme',
        )
        if all_relations:
            relations += (
                'dossiers',
                'filles',
            )
        return relations

    @permalink
    def get_absolute_url(self):
        return 'oeuvre_detail', [self.slug]

    @permalink
    def permalien(self):
        return 'oeuvre_permanent_detail', [self.pk]

    def link(self):
        return self.html(tags=True,
                         auteurs=False,
                         titre=True,
                         descr=True,
                         ancestors=True)

    link.short_description = _('lien')
    link.allow_tags = True

    def get_extrait(self, show_type=True):
        if not self.type_extrait or not self.numero_extrait:
            return ''
        match = self.NUMERO_EXTRAIT_RE.match(self.numero_extrait)
        if match is None:
            return ''
        digits, suffix = match.groups()
        if self.type_extrait in self.TYPES_EXTRAIT_ROMAINS:
            digits = to_roman(int(digits))
        out = f'{digits}{suffix}'
        if self.type_extrait == self.MORCEAU:
            out = ugettext('№ ') + out
        elif self.type_extrait in (self.MOUVEMENT, self.PIECE):
            out += '.'
        elif show_type:
            return f'{self.get_type_extrait_display()} {out}'
        return out

    def caracteristiques_iterator(self, tags=False):
        if self.numero:
            yield ugettext('n° %s') % self.numero
        if self.coupe:
            yield hlp(ugettext('en %s') % self.coupe, ugettext('coupe'), tags)
        if self.incipit:
            yield hlp(
                ugettext('« %s »') % self.incipit, ugettext('incipit'), tags)
        # On ajoute uniquement le tempo s’il n’y a pas besoin de lui dans le
        # titre non significatif, c’est-à-dire s’il y a déjà un genre.
        if self.tempo and self.genre_id is not None:
            yield hlp(self.tempo, ugettext('Tempo'), tags)
        if self.tonalite:
            gamme, note, alteration = self.tonalite
            gamme = GAMMES.get(gamme, '')
            note = self.NOTES[note]
            alteration = self.ALTERATIONS[alteration]
            tonalite = ugettext('en %s') % str_list(
                (em(note, tags), alteration, gamme), ' ')
            yield tonalite
        if self.sujet:
            yield hlp(ugettext('sur %s') % self.sujet, ugettext('sujet'), tags)
        if self.arrangement is not None:
            yield f'({self.get_arrangement_display()})'
        if self.surnom:
            yield hlp(f'({self.surnom})', ugettext('surnom'), tags)
        if self.nom_courant:
            yield hlp(self.nom_courant, ugettext('nom courant'), tags)
        if self.opus:
            yield hlp(ugettext('op. %s') % self.opus, ugettext('opus'), tags)
        if self.ict:
            yield hlp(self.ict, ugettext('Indice de Catalogue Thématique'),
                      tags)

    def caracteristiques_html(self, tags=True):
        return ' '.join(list(self.caracteristiques_iterator(tags=tags)))

    caracteristiques_html.allow_tags = True
    caracteristiques_html.short_description = _('caractéristiques')

    def get_pupitres_str(self, prefix=True, tags=False, solistes=False):
        if not self.pk:
            return ''
        pupitres = self.pupitres.all()
        if solistes:
            pupitres = [p for p in pupitres if p.soliste]

        if not pupitres:
            return ''
        if not prefix:
            return str_list_w_last([p.html(tags=tags) for p in pupitres])

        pupitres_roles = str_list_w_last([
            p.html(tags=tags) for p in pupitres if p.partie.type == Partie.ROLE
        ])
        pupitres_instruments = str_list_w_last([
            p.html(tags=tags) for p in pupitres
            if p.partie.type == Partie.INSTRUMENT
        ])

        if pupitres_roles:
            out = ugettext('de ') + pupitres_roles
            if pupitres_instruments:
                out += ugettext(' avec ') + pupitres_instruments
            return out
        return ugettext('pour ') + pupitres_instruments

    def pupitres_html(self, prefix=False, tags=True, solistes=False):
        return self.get_pupitres_str(prefix=prefix,
                                     tags=tags,
                                     solistes=solistes)

    @model_method_cached()
    def auteurs_html(self, tags=True):
        return self.auteurs.html(tags)

    auteurs_html.short_description = _('auteur(s)')
    auteurs_html.allow_tags = True
    auteurs_html.admin_order_field = 'auteurs__individu__nom'

    def parentes_in_order(self, relation):
        return getattr(self,
                       relation).order_by('creation_date', 'creation_heure',
                                          'creation_lieu',
                                          'creation_date_approx',
                                          'creation_heure_approx',
                                          'creation_lieu_approx')

    def meres_in_order(self):
        return self.parentes_in_order('meres')

    def filles_in_order(self):
        return self.parentes_in_order('filles')

    @property
    def evenements(self):
        # We use a subquery, otherwise yearly_counts counts the number of
        # program elements instead of events.
        return apps.get_model('libretto', 'Evenement').objects.filter(
            pk__in=apps.get_model('libretto', 'Evenement').objects.filter(
                programme__oeuvre__in=self.get_descendants(include_self=True)))

    def oeuvres_associees(self):
        # TODO: Limiter à ce que l’utilisateur peut voir.
        return (Oeuvre.objects.exclude(pk__in=self.get_descendants(
            include_self=True)).filter(
                elements_de_programme__evenement__programme__oeuvre=self).
                annotate(n=Count('elements_de_programme__evenement')).order_by(
                    '-n', *self._meta.ordering)).distinct()

    def _link_with_number(self):
        return ugettext('œuvre jouée %s fois avec : %s') % (self.n,
                                                            self.link())

    def get_referent_ancestors_html(self, tags=False, links=False):
        if not self.pk or self.extrait_de is None or \
                (self.genre and self.genre.referent):
            return ''
        return self.extrait_de.titre_html(tags=tags,
                                          links=links,
                                          ancestors_links=links,
                                          show_type_extrait=False)

    def has_titre_significatif(self):
        return bool(self.titre)

    def get_titre_significatif(self):
        return (f'{self.prefixe_titre}{self.titre}'
                f'{self.coordination}'
                f'{self.prefixe_titre_secondaire}{self.titre_secondaire}')

    def has_titre_non_significatif(self):
        return self.tempo or self.genre_id is not None

    def get_titre_non_significatif(self, tags=True, caps=False):
        if not self.has_titre_non_significatif():
            return ''
        if self.genre is None:
            assert self.tempo != ''
            l = [capfirst(self.tempo) if caps else self.tempo]
        else:
            l = [capfirst(self.genre.nom) if caps else self.genre.nom]
        if not self.has_titre_significatif():
            l.append(self.get_pupitres_str(tags=False, solistes=True))
        l.append(next(self.caracteristiques_iterator(tags=tags), None))

        return str_list(l, infix=' ')

    def get_description(self, tags=True):
        l = []
        if self.has_titre_significatif():
            l.append(self.get_titre_non_significatif(tags=tags))
        caracteristiques = list(self.caracteristiques_iterator(tags=tags))
        if self.has_titre_non_significatif():
            # La première caractéristique est utilisée dans le titre non
            # significatif.
            caracteristiques = caracteristiques[1:]
        l.extend(caracteristiques)
        return str_list(l, infix=' ')

    @model_method_cached()
    def html(self,
             tags=True,
             auteurs=True,
             titre=True,
             descr=True,
             ancestors=True,
             ancestors_links=False,
             links=True,
             show_type_extrait=True):
        l = []
        if auteurs:
            l.append(self.auteurs_html(tags=tags))
        if titre:
            if ancestors:
                l.append(
                    self.get_referent_ancestors_html(tags=tags,
                                                     links=ancestors_links))
            if self.has_titre_significatif():
                titre_complet = cite(self.get_titre_significatif(), tags=tags)
            else:
                titre_complet = self.get_titre_non_significatif(
                    tags=tags,
                    caps=(self.type_extrait is None
                          or self.type_extrait in self.TYPES_EXTRAIT_CACHES))
            extrait = capfirst(self.get_extrait(show_type=show_type_extrait))
            if extrait:
                if titre_complet:
                    titre_complet = f'{extrait} {titre_complet}'
                elif self.type_extrait not in self.TYPES_EXTRAIT_CACHES:
                    titre_complet = extrait
            url = None if not tags else self.get_absolute_url()
            l.append(href(url, titre_complet, tags & links))
        if descr:
            l.append(self.get_description(tags=tags))
        return mark_safe(str_list(l))

    html.short_description = _('rendu HTML')
    html.allow_tags = True

    def short_html(self, tags=True, links=False):
        return self.html(tags=tags,
                         auteurs=False,
                         titre=True,
                         descr=False,
                         ancestors=False,
                         links=links)

    def titre_html(self,
                   tags=True,
                   links=True,
                   ancestors_links=True,
                   show_type_extrait=True):
        return self.html(tags,
                         auteurs=False,
                         titre=True,
                         descr=False,
                         ancestors=True,
                         ancestors_links=ancestors_links,
                         links=links,
                         show_type_extrait=show_type_extrait)

    titre_html.short_description = _('titre')

    def titre_descr(self, tags=False):
        return self.html(tags=tags,
                         auteurs=False,
                         titre=True,
                         descr=True,
                         ancestors=True)

    def titre_descr_html(self):
        return self.titre_descr(tags=True)

    def description_html(self, tags=True):
        return self.html(tags, auteurs=False, titre=False, descr=True)

    def handle_whitespaces(self):
        match = re.match(r'^,\s*(.+)$', self.coordination)
        v = self.coordination if match is None else match.group(1)
        if v:
            self.coordination = f', {v}'
        for attr in ('prefixe_titre', 'prefixe_titre_secondaire',
                     'coordination'):
            v = getattr(self, attr)
            if v and v[-1] not in (' ', "'", '’'):
                setattr(self, attr, f'{v} ')

    def related_label(self):
        txt = force_text(self)
        auteurs = self.auteurs.html(tags=False)
        if auteurs:
            txt += f' ({auteurs})'
        return txt

    def __str__(self):
        return strip_tags(self.titre_html(False))  # strip_tags car on autorise
        # les rédacteurs à mettre des tags dans les CharFields

    _str = __str__
    _str.short_description = _('œuvre')

    @staticmethod
    def autocomplete_search_fields(add_icontains=True):
        lookups = ('auteurs__individu__nom', 'auteurs__individu__prenoms',
                   'auteurs__individu__pseudonyme', 'auteurs__ensemble__nom',
                   'prefixe_titre', 'titre', 'prefixe_titre_secondaire',
                   'titre_secondaire', 'genre__nom', 'numero', 'coupe',
                   'tempo', 'sujet', 'surnom', 'nom_courant', 'incipit',
                   'opus', 'ict', 'pupitres__partie__nom')
        lookups = [f'{lookup}__unaccent' for lookup in lookups]
        if add_icontains:
            return [f'{lookup}__icontains' for lookup in lookups]
        return lookups
Пример #12
0
class TreePlace(Model, TreeModelMixin):
    name = CharField(max_length=50, unique=True, default=get_random_name)
    parent = ForeignKey('self', null=True, blank=True, related_name='children')
    path = PathField()
Пример #13
0
class Migration(migrations.Migration):
    dependencies = [
        ('tree', '0001_initial'),
    ]

    operations = [
        migrations.CreateModel(
            name='MPTTPlace',
            fields=[
                ('id',
                 AutoField(verbose_name='ID',
                           serialize=False,
                           auto_created=True,
                           primary_key=True)),
                ('name',
                 CharField(max_length=50, unique=True,
                           default=get_random_name)),
                ('lft', PositiveIntegerField(db_index=True, editable=False)),
                ('rght', PositiveIntegerField(db_index=True, editable=False)),
                ('tree_id', PositiveIntegerField(db_index=True,
                                                 editable=False)),
                ('level', PositiveIntegerField(db_index=True, editable=False)),
                ('parent', TreeForeignKey('self', blank=True, null=True)),
            ],
            managers=[
                ('_default_manager', Manager()),
            ],
        ),
        migrations.CreateModel(
            name='TreePlace',
            fields=[
                ('id',
                 AutoField(verbose_name='ID',
                           serialize=False,
                           auto_created=True,
                           primary_key=True)),
                ('name',
                 CharField(max_length=50, unique=True,
                           default=get_random_name)),
                ('parent', ForeignKey('self', blank=True, null=True)),
                ('path', PathField()),
            ],
        ),
        CreateTreeTrigger('TreePlace', order_by=('name', )),
        CreateTreeIndex('TreePlace'),
        migrations.CreateModel(
            name='TreebeardALPlace',
            fields=[
                ('id',
                 AutoField(auto_created=True,
                           primary_key=True,
                           serialize=False,
                           verbose_name='ID')),
                ('name',
                 CharField(max_length=50, unique=True,
                           default=get_random_name)),
                ('parent', ForeignKey('self', blank=True, null=True)),
            ],
        ),
        migrations.CreateModel(
            name='TreebeardMPPlace',
            fields=[
                ('id',
                 AutoField(auto_created=True,
                           primary_key=True,
                           serialize=False,
                           verbose_name='ID')),
                ('path', CharField(max_length=255, unique=True)),
                ('depth', PositiveIntegerField()),
                ('numchild', PositiveIntegerField(default=0)),
                ('name',
                 CharField(max_length=50, unique=True,
                           default=get_random_name)),
            ],
        ),
        migrations.CreateModel(
            name='TreebeardNSPlace',
            fields=[
                ('id',
                 AutoField(auto_created=True,
                           primary_key=True,
                           serialize=False,
                           verbose_name='ID')),
                ('lft', PositiveIntegerField(db_index=True)),
                ('rgt', PositiveIntegerField(db_index=True)),
                ('tree_id', PositiveIntegerField(db_index=True)),
                ('depth', PositiveIntegerField(db_index=True)),
                ('name',
                 CharField(max_length=50, unique=True,
                           default=get_random_name)),
            ],
        ),
    ]