2015-11-02 12 views
6

Qual è il modo corretto per estrarre l'anno da un DateTimeField?Ottieni anno da Django DateTimeField con valori()

Esempio dato:

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) 

Cercando di ottenere un elenco di tutti gli anni, e il numero di articoli l'anno:

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

Il risultato è il seguente errore:

near "FROM": syntax error 

La query risultante è:

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

Questa sembra una soluzione complicata a quello che dovrebbe essere un caso d'uso semplice. Estrarre l'anno da un campo datetime non dovrebbe richiedere a uno sviluppatore di scrivere SQL personalizzato. Inoltre, in quale modulo è definito "Func"? –

+0

La soluzione suggerita non funziona.Si è verificato il seguente errore: vicino a "FROM": errore di sintassi –

+0

Questa query viene generata, la sto testando localmente sul database sqlite: SELECT EXTRACT (ANNO DA "articles_article". "Publish_date") AS "Year", COUNT (ESTRATTO (ANNO DA "articles_article". "Publish_date")) AS "dcount" FROM "articles_article" DOVE "articles_article". "Status" = GRUPPO PUBBLICATO PER ESTRATTO (ANNO DA "articles_article". "Publish_date"), "articles_article "." creato "ORDER BY" articles_article "." creato "DESC –

risposta

0

Ho trovato questa domanda molto interessante. Quindi stavo giocando con la console per un po '.

ho seguito i link forniti da @Ivan e ottenuto questo:

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') 

Questo dovrebbe dare il vostro l'anno come una stringa.

Nota: questo funzionerà se in _date si ottiene qualcosa del tipo: u'2015-08-24 09:45:16', se si ottiene una stringa diversa, è possibile modificare gli indici in Substr('_date', 1, 4). Puoi vedere che tipo di stringa ottieni in _date aggiungendola in .values('_date', 'publish_year').

Spero che questo aiuti.

Extra:

Questo è il risultato che ho ottenuto:

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

In questo caso, date3 è per me il risultato finale.

EDIT:

SQL generato:

>>> 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 Questa è una soluzione di alto livello che ho usato solo le funzioni ORM di Django. Ho eseguito il test in un database MySQL, ma non credo che questo causerà problemi su sqlite, ho aggiornato la mia risposta con l'istruzione SQL creata da orm, non sta usando alcuna funzione speciale. – Gocht

+0

Ma EXTRACT non funzionerà su sqlite, ecco perché ho lavorato a questa soluzione con semplici funzioni usando solo ORM. – Gocht

+0

Sembra una buona soluzione, ma quando provo (Django 1.10, usando sqlite) ottengo l'attributo 'AttributeError: 'NoneType' non ha attributo 'tzinfo''. Dal traceback sembra che la query abbia successo, ma Django sta provando a fare una sorta di conversione su quello che pensa sia un campo Datetime, ma in realtà è una stringa come "2015". –

2

Questo è quello che ho finito per fare. La soluzione di Gocht sembra che dovrebbe funzionare ma ho avuto problemi (Django 1.10, SQLite) con Django che gestiva i risultati. Non volevo usare RawSQL ma questa è l'unica cosa che devo lavorare.

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')) 

Questo rende Year un numero intero, anziché una stringa; rimuovere lo CAST(... AS integer) se si desidera una stringa.

È necessario modificare table_name per qualsiasi sia la tabella del database del modello.

Non sono sicuro che lo except NameError sia il modo migliore per gestirlo, ma non sono riuscito a capire come gestirlo meglio.

Problemi correlati