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'), ]
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'), ]
class Place(Model, TreeModelMixin): name = CharField(max_length=50) parent = ForeignKey('self', null=True, blank=True) path = PathField() class Meta: ordering = ('path', 'name')
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'), ]
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')
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)
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', )
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')
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')
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')
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 ' '<em>Lucia di Lammermoor</em> » pour une fantaisie ' 'sur des motifs de <em>Lucia di Lammermoor</em> ' '(<em> et </em> 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
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()
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)), ], ), ]