--- /dev/null
+*.pyc
+*egg-info
+build
+dist
--- /dev/null
+auf.django.metadata
+===================
+
+0.6
+---
+
+* Renommage de metedata par méta-données
+
+0.5
+---
+
+* ajout des verbose_name
+
+* fix delete sur QuerySet
+
+* liste les éléments actifs par défaut dans l'admin
+
+0.4
+---
+
+* Utilisation de DateTimeField à la place de DateField
+
+0.3
+---
+
+* Ajout de managers
+
+0.2
+---
+
+* Ajout dans l'admin, d'une action pour inactiver les objets
+en masse.
+Mettre 'desactiver' dans la actions = () de la liste.
+
+0.1
+---
+
+* Création du module : modèle abstrait avec manager et admin
--- /dev/null
+auf.django.metadata
+===================
+
+Ce module fournit un modèle abstrait en vue d'harmoniser les metadata des modèles
+utilisés dans les applications.
+
+Il s'occupe aussi de l'assignation automatique de ces metadatas à travers l'admin.
+Si les objets sont modifiés à travers un frontend, ces metadatas devront être codées
+manuellement dans les vues.
+
+Ce module fournit notemment une metadata 'supprime', qui permet de simuler une suppression
+aux yeux des utilisateurs. Toute la mécanique des de queryset pour lister et supprimer les
+objets est liée à ce modèle abstrait et n'est plus à coder dans votre application.
+
+Exemple d'utilisation
+=====================
+
+1. Vos models
+-------------
+class AyantDroit(AUFMetadata):
+ """Personne en relation avec un Employe.
+ """
+ nom = models.CharField(max_length=255)
+ prenom = models.CharField(max_length=255, verbose_name="Prénom",)
+ employe = models.ForeignKey('Employe', db_column='employe', verbose_name="Employé")
+
+2. Vos admins
+-------------
+
+# -*- encoding: utf-8 -*-
+
+from django.contrib import admin
+from auf.django.metadata.admin import AUFMetadataAdminMixin
+
+class AyantDroitAdmin(AUFMetadataAdminMixin, admin.ModelAdmin):
+ """
+ L'ajout d'un nouvel ayantdroit se fait dans l'admin de l'employé.
+ """
+
+ # Dans ce cas on ajoute d'autres champs "readonly" autrement cette déclaration
+ # est optionnelle.
+ readonly_fields = AUFMetadataAdminMixin.readonly_fields + ('employe',)
+
+ # Ajoute un premier fieldeset contenant les metadata ordonnées.
+ fieldsets = AUFMetadataAdminMixin.fieldsets + (
+ ("Lien avec l'employé", {
+ 'fields': ('employe', )
+ }),
+
+ ('Identification', {
+ 'fields': ('nom', 'prenom', )
+ }),
+ )
--- /dev/null
+Metadata-Version: 1.0
+Name: auf.django.metadata
+Version: 0.6dev
+Summary: Metadata pour les modèles AUF, avec les outils qui les définissent et les exploitent.
+Home-page: http://pypi.auf.org/auf.django.metadata
+Author: Olivier Larchevêque
+Author-email: olivier.larcheveque@auf.org
+License: GPL
+Description: UNKNOWN
+Platform: UNKNOWN
--- /dev/null
+MANIFEST.in
+README
+setup.cfg
+setup.py
+auf/__init__.py
+auf.django.metadata.egg-info/PKG-INFO
+auf.django.metadata.egg-info/SOURCES.txt
+auf.django.metadata.egg-info/dependency_links.txt
+auf.django.metadata.egg-info/entry_points.txt
+auf.django.metadata.egg-info/not-zip-safe
+auf.django.metadata.egg-info/top_level.txt
+auf/django/__init__.py
+auf/django/metadata/__init__.py
+auf/django/metadata/admin.py
+auf/django/metadata/managers.py
+auf/django/metadata/models.py
+auf/django/metadata/tests.py
+auf/django/metadata/views.py
\ No newline at end of file
--- /dev/null
+
+ # -*- Entry points: -*-
+
\ No newline at end of file
--- /dev/null
+try:
+ __import__('pkg_resources').declare_namespace(__name__)
+except:
+ # bootstrapping
+ pass
--- /dev/null
+try:
+ __import__('pkg_resources').declare_namespace(__name__)
+except:
+ # bootstrapping
+ pass
--- /dev/null
+# -*- encoding: utf-8 -*-
+
+import datetime
+from django.contrib import admin
+from django.contrib import messages
+from django.shortcuts import redirect
+from models import AUFMetadata
+
+AUF_METADATA_READONLY_FIELDS = ('supprime',
+ 'date_creation',
+ 'user_creation',
+ 'date_modification',
+ 'user_modification',
+ 'date_activation',
+ 'user_activation',
+ 'date_desactivation',
+ 'user_desactivation', )
+
+AUF_METADATA_FIELDSET_FIELDS = ('actif', 'date_creation', 'user_creation',
+ 'date_modification', 'user_modification',
+ 'date_activation', 'user_activation',
+ 'date_desactivation', 'user_desactivation',)
+
+class AUFMetadataInlineAdminMixin(object):
+ exclude = AUF_METADATA_READONLY_FIELDS + ('actif', )
+
+
+class AUFMetadataAdminMixin(object):
+ """
+ Surcharge l'admin de base, pour setter automatiquement les metadata.
+ """
+ actions = ['desactiver', ]
+
+ readonly_fields = AUF_METADATA_READONLY_FIELDS
+ fieldsets = (
+ ('Méta-données', {
+ 'classes': ('collapse',),
+ 'fields': AUF_METADATA_FIELDSET_FIELDS,
+ }),
+ )
+
+ def desactiver(modeladmin, request, queryset):
+ """
+ Passe à actif = False tous les objets du QS
+ """
+ selected = request.POST.getlist(admin.ACTION_CHECKBOX_NAME)
+ queryset.update(actif=False)
+ messages.add_message(request, messages.INFO, u'Les %s ont été désactivés.' % queryset.model._meta.verbose_name_plural)
+ info = queryset.model._meta.app_label, queryset.model._meta.module_name
+ return redirect('admin:%s_%s_changelist' % info)
+ desactiver.short_description = u"Désactiver les éléments sélectionnés"
+
+ def save_model(self, request, obj, form, change):
+ if obj.user_creation is None:
+ obj.user_creation = request.user
+ obj.date_creation = datetime.datetime.now()
+ obj.user_modification = request.user
+ obj.date_modification = datetime.datetime.now()
+ initial = getattr(form, 'initial', None)
+ if initial:
+ if form.initial['actif'] is True and obj.actif is False:
+ obj.user_desactivation = request.user
+ obj.date_desactivation = datetime.datetime.now()
+ if form.initial['actif'] is False and obj.actif is True:
+ obj.user_activation = request.user
+ obj.date_activation = datetime.datetime.now()
+ obj.save()
+
+ def has_metadata(self, instance):
+ """
+ Test si une instance de modele hérite bien de AUFMetadata.
+ """
+ test = getattr(instance, 'supprime', None)
+ return test is not None
+
+ def save_formset(self, request, form, formset, change):
+ """
+ Mettre les metadata si l'objet est créé ou modifié par inline.
+ """
+ instances = formset.save(commit=False)
+ for instance in instances:
+ if self.has_metadata(instance):
+ self.save_model(request, instance, formset.form, change)
+ else:
+ instance.save()
+ formset.save_m2m()
+
+
+ def changelist_view(self, request, extra_context=None):
+ """
+ Sans filtre sélectionné, on redirige vers les éléments actifs
+ """
+ url = request.get_full_path()
+ has_filter = "?" in url
+ if has_filter is False:
+ q = request.GET.copy()
+ q['actif__exact'] = 1
+ return redirect("%s?%s" % (url, q.urlencode()))
+ return super(AUFMetadataAdminMixin, self).changelist_view(request, extra_context=extra_context)
+
--- /dev/null
+# -*- encoding: utf-8 -*-
+
+from django.db import models
+
+
+class NoDeleteQuerySet(models.query.QuerySet):
+ """
+ Pas de delete, flag à supprimer sur les entrées.
+ """
+ def delete(self):
+ self.update(supprime=True)
+
+
+class NoDeleteManager(models.Manager):
+ """
+ Les entrées supprimées sont exclues des querysets.
+ """
+ def get_query_set(self):
+ return NoDeleteQuerySet(self.model, using=self._db).filter(supprime=False)
+
+
+class ActifsManager(NoDeleteManager):
+ """
+ Seulement les entrées actives
+ """
+ def get_query_set(self):
+ return super(ActifsManager, self).get_query_set().filter(actif=True)
+
+
+class InactifsManager(NoDeleteManager):
+ """
+ Seulement les entrées inactives
+ """
+ def get_query_set(self):
+ return super(InactifsManager, self).get_query_set().filter(actif=False)
--- /dev/null
+# -*- encoding: utf-8 -*-
+
+from django.db import models
+from managers import NoDeleteManager, ActifsManager, InactifsManager
+
+
+class AUFMetadata(models.Model):
+ """
+ Méta-données AUF.
+ supprime = niveau système
+ actif = niveau utilisateur
+ """
+ supprime = models.BooleanField(default=False)
+ date_creation = models.DateTimeField(null=True, blank=True, verbose_name=u"Date de création",)
+ user_creation = models.ForeignKey('auth.User', verbose_name=u"Crée par",
+ db_column='user_creation', related_name='+',
+ null=True, blank=True)
+ date_modification = models.DateTimeField(null=True, blank=True, verbose_name=u"Date de modification",)
+ user_modification = models.ForeignKey('auth.User', verbose_name=u"Modifié par",
+ db_column='user_modification', related_name='+',
+ null=True, blank=True)
+ actif = models.BooleanField(default=True)
+ date_activation = models.DateTimeField(null=True, blank=True, verbose_name=u"Date d'activation",)
+ user_activation = models.ForeignKey('auth.User', verbose_name=u"Activé par",
+ db_column='user_activation', related_name='+',
+ null=True, blank=True)
+ date_desactivation = models.DateTimeField(null=True, blank=True, verbose_name=u"Date de désactivation",)
+ user_desactivation = models.ForeignKey('auth.User', verbose_name=u"Désactivé par",
+ db_column='user_desactivation', related_name='+',
+ null=True, blank=True)
+
+ objects = NoDeleteManager()
+ actifs = ActifsManager()
+ inactifs = InactifsManager()
+
+ class Meta:
+ abstract = True
+
+ def delete(self):
+ self.supprime = True
+ self.save()
+
+
--- /dev/null
+"""
+This file demonstrates two different styles of tests (one doctest and one
+unittest). These will both pass when you run "manage.py test".
+
+Replace these with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+class SimpleTest(TestCase):
+ def test_basic_addition(self):
+ """
+ Tests that 1 + 1 always equals 2.
+ """
+ self.failUnlessEqual(1 + 1, 2)
+
+__test__ = {"doctest": """
+Another way to test that 1 + 1 is equal to 2.
+
+>>> 1 + 1 == 2
+True
+"""}
+
--- /dev/null
+# Create your views here.
--- /dev/null
+[egg_info]
+tag_build = dev
+tag_svn_revision = true
--- /dev/null
+# -*- encoding: utf-8 -*-
+
+from setuptools import setup, find_packages
+import sys, os
+
+name = 'auf.django.metadata'
+version = '0.6'
+
+setup(name=name,
+ version=version,
+ description="Metadata pour les modèles AUF, avec les outils qui les définissent et les exploitent.",
+ long_description="""\
+""",
+ classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
+ keywords='',
+ author='Olivier Larchev\xc3\xaaque',
+ author_email='olivier.larcheveque@auf.org',
+ url='http://pypi.auf.org/%s' % name,
+ license='GPL',
+ packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
+ include_package_data=True,
+ zip_safe=False,
+ install_requires=[
+ # -*- Extra requirements: -*-
+ ],
+ entry_points="""
+ # -*- Entry points: -*-
+ """,
+ )