Was tun, wenn Djangos ORM zu einfach ist?

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Samstag 25. Oktober 2008, 20:27

Ich baue mir gerade etwas wie Ubuntu Brainstorm. Dort können Anwender Vorschläge machen, bewerten und kommentieren. Nun möchte ich Listen von Vorschlägen basierend auf der Aktivität der Anwender berechnen.

Mein Django-Modell sieht so aus:

Code: Alles auswählen

class Proposal(models.Model):
    author = models.ForeignKey(User, related_name='proposals')
    created_at = models.DateTimeField(editable=False)
    description = models.TextField()
    ...
    def vote_up(self, user): self._vote(user, +1)
    def vote_down(self, user): self._vote(user, -1)
    
    def _vote(self, user, value):
        vote, created = self.votes.get_or_create(voter=user, defaults={...})
        if not created:
            vote.value = value
            vote.created_at = datetime.datetime.now
            vote.save()

class Vote(models.Model):
    voter = models.ForeignKey(User, related_name='votes')
    proposal = models.ForeignKey(Proposal, related_name='votes')
    created_at = models.DateTimeField(editable=False)
    value = models.IntegerField(choices=(-1, +1))
    ...
Um die Vorschläge nach der Höhe der Stimmwerte zu sortieren, schummle ich und speichere in `Proposal` diesen Wert, den ich nach jeder Änderung in `Vote` aktualisiere. Das SQL dazu sieht so aus, was ich mit Djangos ORM dank `extra()` gerade eben noch hinbekomme.

Code: Alles auswählen

select sum(value) from ..._vote where proposal_id = ?
Die Liste aller Vorschläge, sortiert nach dem Stimmwert, ohne diesen Wert zu speichern, sähe wohl so aus:

Code: Alles auswählen

select p.*, sum(value) as s 
  from {Proposal} p, {Vote} v
 where v.proposal_id = p.id
 group by v.proposal_id
 order by s
Will ich die Vorschläge finden, bei denen in den letzten 7 Tagen die meiste Aktivität war, muss ich `sum` in `count` ändern und noch ein `and v.created_at >= ?` einfügen, wobei `?` für einen Zeitpunkt vor 7 Tagen steht, oder? Habe ich irgendeine Chance, das auch in Django auszudrücken?

Wie finde ich alle Vorschläge, die zuletzt kommentiert wurden? Was wären sonst noch sinnvolle Abfragen, damit nicht immer nur die beliebtesten Vorschläge zu sehen sind, die dadurch noch beliebter werden?

Bitte keine Vorschläge, wie ich das mit SQLalchemy o.ä. ermitteln kann, denn das kann und will ich aus Django nicht so ohne weiteres einsetzen.

Hat jemand stattdessen schon mal damit experimentiert, SQL-artige Ausdrücke wie den da oben automatisch in backend-spezfisches SQL umzuschreiben? Ich möchte eigentlich nicht Tabellennamen und Spaltennamen hart verdrahten, sondern immer über die Django-Modelle gehen.

Ich wäre bereit zu garantieren, die Zeilenumbrüche immer so zu machen, sodass es auch mit regulären Ausdrücken recht einfach sein sollte, `from` und ähnliches zu finden. Etwas in geschweiften Klammern (oder meinetwegen auch Backquotes) wäre dann entweder der Klassenname eines Modells oder ein Attributname, wobei man dann alle beteiligten Modelle durchsucht oder eine Punkt-Notation benutzen muss. Gibt es schon so was in fertig oder muss man da selbst ran?

Ist überhaupt klar, was ich meine? ;)

Stefan
bracki
User
Beiträge: 4
Registriert: Montag 27. Oktober 2008, 11:13

Montag 27. Oktober 2008, 11:16

http://docs.djangoproject.com/en/dev/re ... ets/#range

Mit *filter* und einer Range könnte das doch funktionieren, oder? Wobei du es wahrscheinlich schon ausprobiert hast...
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Mittwoch 29. Oktober 2008, 09:37

Das Problem ist nicht, ein BETWEEN-AND zu erzeugen, sondern SELECT-Anweisungen mit COUNT, SUM, GROUP BY und HAVING. Aggregatorfunktionen sollen zwar mit Django 1.1 kommen, aber das ist noch 6 Monate hin.

Direktes SQL finde ich blöd, weil ich dann wissen muss, wie Django Spalten und Tabellen benannt hat. Außerdem muss ich Änderungen an diesen Namen dann in allen SQL-Anweisungen nachziehen. Das ist nicht DRY. Schließlich kann es mir passieren, dass ich Modell- oder Attributnamen habe, die reservierten Namen in SQL entsprechen. Django kümmert sich darum und weiß, wie man da (datenbankspezifisch) die Namen "escaped". Wenn ich das selbst mache, muss ich mich auf eine Datenbank festlegen.

Ich habe mir daher eine (recht komplizierte) Funktion geschrieben, die mittels regulären Ausdrücken nach in {} stehenden Attributnamen und Modellnamen sucht und (hoffentlich ähnlich wie Django - ich habe den Code da nicht so genau gefunden) in die DB-spezfischen Namen umwandelt.

Eine Alternative ist möglicherweise, das eigentlich nicht dokumentierte django.db.models.sql-Paket zu benutzen, welches offenbar für QuerySets benutzt wird. Dort habe ich aber auf die Schnelle nicht die gewünschten Hilfsfunktionen gefunden. Ich hätte mir eigentlich schon Model und Field Methoden gewünscht, die mir SQL-Fragmente erzeugen.

Nun ja, es bleibt leider kompliziert.

Stefan
Benutzeravatar
jens
Moderator
Beiträge: 8461
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Mittwoch 29. Oktober 2008, 09:48

Ich hab mir das nicht näher angesehen, aber ich denke das django ORM ist nicht gerade dafür konzipiert mal eben neue Funktionen einzubauen. Schade eigentlich :(

Wenn man kein eigenen SQL Befehl bauen möchte, dann bleibt nur die vorhandenen ORM Funktionen zu nutzten und die Daten mit Python zu bearbeiten. Natürlich ist das umständlicher und wahrscheinlich nicht so performant...

EDIT: Hab das Thema SQLAlchemy abgetrennt: http://www.python-forum.de/topic-16516.html

CMS in Python: http://www.pylucid.org
GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Antworten