2015-11-02 5 views
6

Was ist der richtige Weg, um das Jahr aus einem DateTimeField zu extrahieren?Erhalten Jahr von Django DateTimeField mit Werten()

Beispiel gegeben:

class Article(models.Model): 
    title = models.CharField(_('title'), max_length=250) 
    slug = models.SlugField(_('slug'), max_length=250, unique=True, default='', blank=True) 
    content = models.TextField(_('content')) 
    author = models.ForeignKey(settings.AUTH_USER_MODEL) 
    categories = models.ManyToManyField(Category) 
    tags = models.ManyToManyField(Tag) 
    created = models.DateTimeField(_('created'), default=timezone.now) 
    publish_date = models.DateTimeField(_('publish date'), blank=True, null=True) 

    STATUS_ARTICLE = (
     ('DRAFT', _('draft')), 
     ('PUBLISHED', _('published')) 
    ) 

    status = models.CharField(_('status'), max_length=100, choices=STATUS_ARTICLE, default='DRAFT') 

class ExtractMonth(Func): 
    template = "EXTRACT(MONTH FROM %(expressions)s)" 

    def __init__(self, *expressions, **extra): 
     extra['output_field'] = models.SmallIntegerField() 
     super().__init__(*expressions, **extra) 

eine Liste von all den Jahren zu erhalten versuchen, und die Anzahl der Artikel pro Jahr:

result = Article.objects.filter(status='PUBLISHED').annotate(Year=ExtractYear('publish_date')).values('Year').annotate(dcount=Count('Year')) 

Dieses in den folgenden Fehlern führt:

near "FROM": syntax error 

Die resultierende Abfrage lautet:

SELECT EXTRACT(YEAR FROM "articles_article"."publish_date") AS "Year", COUNT(EXTRACT(YEAR FROM "articles_article"."publish_date")) AS "dcount" FROM "articles_article" WHERE "articles_article"."status" = PUBLISHED GROUP BY EXTRACT(YEAR FROM "articles_article"."publish_date"), "articles_article"."created" ORDER BY "articles_article"."created" DESC 
+0

Dies scheint eine komplizierte Lösung zu sein, was ein einfacher Anwendungsfall sein soll. Das Extrahieren des Jahrs aus einem datetimefield sollte keinen Entwickler erfordern, um benutzerdefiniertes SQL zu schreiben. Auch in welchem ​​Modul ist "Func" definiert? –

+0

Lösungsvorschlag funktioniert nicht.Der folgende Fehler ist aufgetreten: in der Nähe von "FROM": Syntaxfehler –

+0

Die Abfrage, die generiert wird, teste ich dies lokal in der SQLite-Datenbank: SELECT EXTRACT (JAHR VON "articles_article". "Publish_date") AS "Year", COUNT (EXTRACT (JAHR VON "articles_article". "Publish_date")) AS "dcount" VON "articles_article" WHERE "articles_article". "Status" = VERÖFFENTLICHTE GROUP BY EXTRACT (JAHR von "articles_article". "Publish_date"), "articles_article "." erstellt "ORDER BY" articles_article "." created "DESC –

Antwort

0

Ich fand diese Frage sehr interessant. Ich spielte also eine Weile mit der Konsole.

Ich folgte die von @Ivan ausgebrachten Links und bekam dies:

from django.db.models import F, Func 
from django.db.models.functions import Substr 

Article.objects.filter(...).annotate(_date=Func(F('publish_date'), function='LOWER')) 
          .annotate(publish_year=Substr('_date', 1, 4)) 
          .values('publish_year') 

Diese Sie das Jahr als String geben soll.

Hinweis: Dies funktioniert, wenn in _date Sie etwas wie folgt erhalten: u'2015-08-24 09:45:16', wenn Sie eine andere Zeichenfolge erhalten, können Sie die Indizes in Substr('_date', 1, 4) ändern. Sie können sehen, welche Art von Zeichenfolge Sie in _date erhalten, indem Sie es in .values('_date', 'publish_year') hinzufügen.

Ich hoffe, das hilft.

Extra:

Dies ist das Ergebnis, das ich bekam:

[{'date': datetime.datetime(2015, 8, 24, 9, 45, 16), 'date3': u'2015', 'date2': u'2015-08-24 09:45:16'}, ...] 

In diesem Fall date3 für mich ist das Endergebnis.

EDIT:

generiert SQL:

>>> print MyModel.objects.all().annotate(date2=Func(F('date'), function='LOWER')).annotate(date3=Substr('date2', 1, 4)).query 
SELECT `app_model`.`id`, `app_model`.`date`, LOWER(`app_model`.`date`) 
AS `date2`, SUBSTRING(LOWER(`app_model`.`date`), 1, 4) 
AS `date3` FROM `app_model` ORDER BY `app_model`.`created` ASC 
+0

@Ivan Dies ist eine High-Level-Lösung Ich habe nur Django ORM-Funktionen verwendet. Ich testete in einer MySQL-Datenbank, aber ich glaube nicht, dass dies Probleme bei sqlite verursachen wird. Ich habe meine Antwort mit der SQL-Anweisung aktualisiert, die aus dem ORM erstellt wurde, sie verwendet keine spezielle Funktion. – Gocht

+0

Aber EXTRACT wird nicht auf sqlite funktionieren, deshalb habe ich an dieser Lösung mit einfachen Funktionen gearbeitet, die nur ORM verwenden. – Gocht

+0

Das sieht nach einer schönen Lösung aus, aber wenn ich es versuche (Django 1.10, mit sqlite) bekomme ich 'AttributeError: 'NoneType' Objekt hat kein Attribut 'tzinfo''. Aus dem Traceback sieht es so aus, als würde es die Abfrage erfolgreich durchführen, aber dann versucht Django, eine Art Konvertierung für das zu tun, was es für ein Datetime-Feld hält, ist aber tatsächlich eine Zeichenkette wie "2015". –

2

Dies ist, was ich am Ende habe zu tun. Göcht's Lösung sieht so aus, als ob es funktionieren sollte, aber ich hatte Probleme (Django 1.10, SQLite) mit Django, der die Ergebnisse behandelt. Ich wollte nicht RawSQL verwenden, aber das ist das einzige, was ich arbeiten muss.

try: 
    from django.db.models.functions import ExtractYear 
except ImportError: 
    from django.db.models.expressions import RawSQL 

qs = Article.objects.filter(...) 

try: 
    # Django >= 1.10 
    qs = qs.annotate(Year=ExtractYear('publish_date')) 
except NameError: 
    # Django < 1.10 
    qs = qs.annotate(Year=RawSQL(
      "CAST(SUBSTR('table_name'.'publish_date', 1, 4) AS integer)", 
      () 
     )) 

results = qs.values('Year').annotate(dcount=Count('Year')) 

Dies macht Year eine ganze Zahl, sondern als eine Zeichenfolge; Entfernen Sie die CAST(... AS integer), wenn Sie eine Zeichenfolge möchten.

Sie müssen die table_name ändern, um was auch immer die Datenbanktabelle Ihres Modells ist.

Ich bin nicht sicher, dass die except NameError ist der beste Weg, damit umzugehen, aber ich konnte nicht herausfinden, wie besser zu handhaben.