Freitag, 3. August 2012

Django Objekt Ableitung

Große und komplexe Anwendung mit Businesslogik erfordert auch komplexere Daten-Modelle, in manchen Fällen dürfen Daten nicht phsysikalisch gelöscht werden, sondern werden nur als gelöscht markiert, oder man möchte wissen wann etwas hinzugefügt und wann es geändert wurde.
Sortierung gehört auch dazu.
Bei Anwendungen mit zig Apps und hunderten von Models ist es nicht sinnvoll jedes Model anzufassen und denen immer wieder die gleichen Attribute wie "created", "updated" bzw. "sorting" einzutragen. Abgesehen davon verstößt man da gegen das DRY-Prinzip und man macht sich unnötig viel Arbeit, spätestens wenn weitere Attribute hinzukommen,  gelöscht oder umbenannt werden.

In den 5 Jahren (2007) Django Entwicklung habe ich einige Strategien untersucht und eine hat sich als am praxistauglichsten herausgestellt, Abstrakte-Klassen.
Dabei ist django sehr freundlich was das Ableiten von abstrakte-Klassen angeht, es verhält sich exakt so wie man es von eine objekt-orientierte-sprache erwartet und überträgt dieser durch den ORM in die Datenbank.

Hier die Klassen.

Die Klasse "DjObject" ist unsere Basis, sie besitzt die Attribute, hidden (falls ein Datensatz nur als unsichtbar markiert werden soll), deleted (wenn ein Datensatz gelöscht wird - "Mülltonne", es ist noch da wird aber nirgends angezeigt).
Da kommt der DjObjectManager ins Spiel, solange man den Manager über XX.objects.visible() aufruft werden nur nicht gelöschte Datensätze gefiltert und das für ALLE Klassen die von DjObject ableiten - ist das nicht cool?.

Jetzt gehen wir ein Schritt weiter und fügen die Klasse "Sortable" hinzu, diese hat nur das Attribut "sorting", gleichzeitig hat es auch alle Attribute und Funktionen von DjObject.
Damit das Sorting automatisch für alle abgeleiteten Klassen hochzählt müssen wir die Save-Funktion erweitern.
Beim Sortieren hilft uns Django wieder aus, mit der Meta-Klasse kann der django-default-manager angewiesen werden nach dem Attribut "sorting" zu sortieren.

from django.db import models

class DjObjectManager(models.Manager):
    def visible(self):
        return self.filter(hidden = False, deleted = False)
    

class DjObject(models.Model):
    hidden = models.BooleanField( default = False )
    deleted = models.BooleanField (default = False )
    created = models.DateTimeField(auto_now_add = True )
    updated = models.DateTimeField( auto_now = True )
    objects = DjObjectManager()

    def hide(self):
        self.hidden = True
        self.save()
    
    def show(self):
        self.hidden = False
        self.save()
    
    def remove(self):
        self.hidden = True
        self.deleted = True
        self.save()
    
    class Meta:
        abstract = True
        
        

class Sortable(DjObject):
    sorting = models.IntegerField( blank = True, null = True )
    sorting_increment = 10    

    def save(self, *args, **kwargs):
        self.sorting = self.__class__.objects.count() + self.sorting_increment 
        super(Sortable, self).save(*args, **kwargs)
    
    class Meta:
        ordering = ("sorting", )
        abstract = True


Jetzt leiten wir mal eine konkrete Klasse von den Abtraktenklassen ab.


class Country(Sortable):
    name = models.CharField(max_length=255)
    
    def __unicode__(self):
        return self.name

class Address(DjObject):
    name = models.CharField(max_length = 255)
    descrription = models.TextField(blank = True, null = True)
    
    def __unicode__(self):
        return self.name


Der django-orm-Mapper löst die Klasse so auf
Die Attribute der Abtrakten-Klassen werden auf die Konkrete Klasse übertragen, in diesem Falle sind es die Attribute von DjObject und Sortable, zuletzt werden die Klassen-attribute hinzugefügt, in diesem Falle "name".



CREATE TABLE app_country
(
  id serial NOT NULL,
  hidden boolean NOT NULL,
  deleted boolean NOT NULL,
  created timestamp with time zone NOT NULL,
  modified timestamp with time zone NOT NULL,
  sorting integer,
  name character varying(255) NOT NULL,
  CONSTRAINT app_country_pkey PRIMARY KEY (id )
)


CREATE TABLE app_address
(
  id serial NOT NULL,
  hidden boolean NOT NULL,
  deleted boolean NOT NULL,
  created timestamp with time zone NOT NULL,
  modified timestamp with time zone NOT NULL,
  name character varying(255),
  description text
)


Jetzt können wir auch auf die Funktionen der Abstrakte-klassen zugreifen:

Company.objects.all() # django-default-manager

Company.objects.visible() # djobject-manager

company = Company.objects.create(name="XXX") # aufruf von save - zaehlt sorting hoch

company.hide() # das Objekt unsichtbar machen

company.show() # object wieder sichtbar machen

company.remove() # als geloescht markieren 


Hiermit kommt man schon ziemlich weit, es gibt noch komplexere Anwendungsfälle die komplexere Strukturen benötigen, das hier reicht für 99% der Anwendungen aus.

dann viel spaß beim Testen

und gerne könnt ihr mir einen Kommentar hinerlassen.











Keine Kommentare:

Kommentar veröffentlichen