Django Admin: Popup nach Änderung schließen

Veröffentlich am 18.11.2008, 14:24

Ich arbeite derzeit an einem netten CMS Modul für Django, bei dem die einzelnen Seiten im Baukastenprinzip inkl. Drag+Drop zusammengesetzt werden. Beim Bearbeiten der einzelnen PageElements (die Blöcke, aus denen die Seite besteht) fiel mir auf, dass beim Speichern der REQUEST Parameter _popup nicht berücksichtigt wird, sondern stattdessen zur Change List des zu bearbeitenden Models gewechselt wird.

Dieses Stück Code schafft Abhilfe und schließt das Popup Window nach dem Speichern auf die gleiche Weise, wie beim Hinzufügen von Objekten bei z.b. ForeignKeys.

class ClosablePopupAdmin(admin.ModelAdmin):
    def response_change(self, request, obj):
        response = super(ClosablePopupAdmin, self).response_change(request, obj)
        if request.POST.has_key("_popup"):
            return HttpResponse('<script type="text/javascript">window.close();</script>')
        return response

Sehr simpel aber funktioniert ;-)

Tags: django, newforms-admin, popup

Weiterlesen, 0 Kommentar(e)

 

"Nächste Geburtstage" Snippet

Veröffentlich am 08.11.2008, 18:24

Ein kurzes Snippet zum Anzeigen der nächsten Geburtstage.
Dieser Codeausschnitt benötigt ein Profile Model mit einem birthday Feld (DateField oder DateTimeField) sowie einem ForeignKey auf django.contrib.auth.models.User mit dem Namen user.

Die Methode nimmt als Mengen-Parameter amount an, standardmäßig ist 5 gesetzt. Als Rückgabewert wird eine Liste mit den django.auth.models.User Objekten ausgegeben.

from myproject.accounts.models import Profile
import datetime

def next_birthdays(amount=5):
    birthdays = {}
    for profile in Profile.objects.select_related('user'):
        birthdays.setdefault((profile.birthday.month, profile.birthday.day), []).append(profile.user)
    dates = birthdays.keys()
    dates.sort()

    for i, date in enumerate(dates):
        if date >= (datetime.date.today().month, datetime.date.today().day):
            total = len(dates)
            first_pos = total - i
            break

    sorted_birthdays = dates[total - first_pos:]
    sorted_birthdays.extend(dates[:total - first_pos])

    output = []
    for date in sorted_birthdays:
        for user in birthdays[date]:
            output.append(user)
            if len(output) >= amount:
                return output
    return output

Es wird nur ein Datenbank Query abgefragt jedoch ist die Funktion relativ rechenintensiv. Ich empfehle hier unbedingt, die Ausgabe zu cachen.

Tags: dates, django, snippet

Weiterlesen, 0 Kommentar(e)

 

DB Update für Django, ein Konzept

Veröffentlich am 08.11.2008, 11:17

Ich habe mir heute morgen einige Schema Evolution Tools für Django angesehen. django-evolution fällt aufgrund schlechter Erfahrung aus, dmigrations kommt nicht in Frage da nur MySQL unterstützt wird.

Bis jetzt mache ich meine Datenbank-Änderungen manuell mit einem Ordner voller sql Dateien, für jede Änderung eine Datei. Das klappt auch super, einziges Manko: man muss wissen, bei welcher "Version" man mit dem ausführen der sql Dateien beginnt.

Diesen Vorgang möchte ich nun automatisieren. Grundsätzlich muss erwähnt werden, das ich solch ein Tool nur für das Aktualisieren von Webseiten, die sich bereits im Produktionsbetrieb befinden, verwenden würde. Zum Zeitpunkt der Entwicklung ist die Erstellung der Änderungsscripte zu aufwändig.

Die Anwendung und Vorgehensweise stelle ich mir wie folgt vor:

Es existiert ein Model welches für jedes Model die aktuelle Version speichert:

class ModelVersion(models.Model):
    model = models.ForeignKey(ContentType, unique=True)
    version = models.PositiveIntegerField()

.. für in jeder Application existiert ein Python Modul "dbupdates" welches ein Dictionary CHANGES enthält:

CHANGES = {
    1, (
        'ATLER TABLE myapp_mymodel ADD COLUMN ...',
    ),
    2, (
        'ALTER TABLE myapp_mymodel DROP COLUMN ...',
        'DROP TABLE ...',
    ),
}

.. um Änderungen auszuführen, wird folgender Befehl ausgeführt:

alle Anwendungen aktualisieren:
./manage dbupdate

oder nur eine Anwendung aktualisieren:
./manage dbupdate [application]

Alternativ kann über den Parameter --startat=version die Startversion übergeben werden (ich merkte bereits an, das die Version zum Zeitpunkt des Checkouts bekannt sein muss, dannach wird die aktuelle Version in der Datenbank gespeichert.

Möglich ist auch, die CHANGES optional Datenbank-abhängig zu gestalten um auf eigenarten gewisser RDBMS einzugehen.

Soweit meine Idee, Feedback ist ausdrücklich erwünscht. Entweder als Kommentar, per Mail oder im IRC (#django-de / Freenode).

Nachtrag: Ich muss feststellen, dass ich hier bis auf wenige Kleinigekeiten dmigrations "nachbaue". Jedoch sehe ich derzeit keine andere Möglichkeit, da dmigrations ausschließlich mit MySQL arbeitet. Abgesehen davon wäre dmigrations jedoch genau das richtige für meine Anwendungszwecke.

Tags: database, dbupdate, django, schema evolution

Weiterlesen, 0 Kommentar(e)

 

AdminFileWidget mit Delete-Funktion

Veröffentlich am 06.11.2008, 10:00

Nachdem ich bereits längere Zeit nach einer Möglichkeit gesucht habe, den Inhalt von File-Feldern über Newforms-Admin löschen zu können, habe ich nun selbst ein kleines Widget geschrieben, welches genau das erledigt.

Neugeschrieben? Manch einer wird sich fragen, warum ich nicht eines der existierenden Snippets (#1, #2, es gibt sicher noch weitere) von djangosnippets.org nutze. Ganz einfach: sie machen eine Änderung des Models notwendig und genau das will ich nicht. Ich möchte meine Models nicht aufgrund eines Admin-Features ändern müssen.

Ich beginne erstmal mit dem Code selbst, Erläuterungen folgen:

class AdminDeleteFileWidget(admin.widgets.AdminFileWidget):
    def render(self, name, value, attrs=None):
        output = []
        output.append(super(AdminDeleteFileWidget, self).render(name, value, attrs))
        output.append('<br /><input type="checkbox" name="adfw_%s_delete" /> %s' % (name, 'Delete'))
        return mark_safe(u''.join(output))

class DeleteModelAdmin(admin.ModelAdmin):
    def response_change(self, request, obj):
        from django.db.models.fields.files import FileField
        for field in obj._meta.fields:
            if isinstance(field, FileField):
                if request.POST.has_key('adfw_%s_delete' % field.name):
                    try:
                        file = getattr(obj, field.name)
                        file.delete()
                    except:
                        pass

        return super(DeleteModelAdmin, self).response_change(request, obj)

    def formfield_for_dbfield(self, db_field, **kwargs):
        field = super(DeleteModelAdmin, self).formfield_for_dbfield(db_field, **kwargs)
        if field and isinstance(field.widget, admin.widgets.AdminFileWidget):
            field.widget = AdminDeleteFileWidget()
        return field

Der Aufbau ist relativ simpel. Ich habe die AdminFileWidget Klasse von Newforms-Admin beerbt und an die reguläre Ausgabe noch eine Checkbox zum Löschen angehängt. Der Name ist bewusst kompliziert gewählt, um zu vermeiden das ein normales Model-Feld überschrieben wird (adwf_feldname_delete).

Beim laden des Formulars wird für jedes Model-Feld die Methode formfield_for_dbfield aufgerufen. Diese Methode wird durch die DeleteAdmin Klasse überschrieben und prüft für jedes Feld, ob es sich um ein File-Feld handelt. Wenn ja, wird das Widget durch das AdminDeleteFileWidget ausgetauscht.

Beim Speichern des Formulars wird die Methode response_change aufgerufen. Hier prüft die Methode für jedes Model-Feld, ob es sich um ein File-Feld handelt und wenn dies zutrifft prüft die Methode weiter, ob das eine Datei hinterlegt ist. Ist das der Fall, wird das Vorhandensein der POST Variable adfw_feldname_delete abgefragt und bei erfolg die Datei gelöscht.

Um diese Funktionalität einsetzen zu können, müssen die Model-spezifischen Adminklassen von DeleteAdmin statt admin.ModelAdmin erben.

Tags: django, newforms-admin, widget

Weiterlesen, 0 Kommentar(e)

 

ProgressBar leakt user handles

Veröffentlich am 13.03.2008, 07:59

.. ich werde noch verrückt hier, seit gestern Nachmittag bin ich auf der Suche nach einem recht eigenartigen Problem und nun habe ich den Übeltäter endlich gefunden.

In einem meiner Kundenprojekte läuft unter anderem eine sehr komplexe GUI für die Erfassung und statistische Auswertung diverser Daten. Für die Auswertung verwende ich an verschiedenen Stellen ProgressBars aus dem Standardsortiment von Microsofts .NET Libraries. (warum ich gerade die ProgressBars ansprechen? dazu kommen wir gleich).

Das Programm hat immer mal wieder win32Exceptions ala "Cannot create window handle" geworfen. Sehr aufschlussreich - muss ich schon sagen.

Nach einiger Recherche ist mir dann aufgefallen, dass der NativeErrorCode "14" ist. Dieser bedeutet soviel wie "OutOfMemory". Da die Testmaschine 1GB Ram hat und außer Windows+Updates nichts installiert ist, konnte ich das beim besten Willen nicht glauben.

Ich habe mich dann auf die Suche nach dem Übeltäter gemacht und mit Hilfe durch den ProcessExplorer festgestellt, dass das Programm immer mehr USER Handles vom System frisst.. und irgendwann ist Schluss. GDI wirft dann die besagte "Cannot create window handle" Exception.

Durch Try'n'Error habe ich dann den Täterkreis immer weiter gegrenzt und am Ende blieben nur noch die ProgressBars übrig (hat eine Weile gedauert, in der GUI liegen ca. 1400 Controls rum). Scheinbar disposen die ProgressBars nicht ordentlich, anders kann ich mir die unmenschlich hohe Anzahl an Userhandles derzeit nicht erklären.

Das Ende vom Lied? Ich zeichne mir die ProgressBars nun selbst, ist eh "cooler" ;-)

Tags: .net, c#, gdi

Weiterlesen, 0 Kommentar(e)