From 230671ff54cb38b2ef390833145132ba721a054e Mon Sep 17 00:00:00 2001 From: Eric Mc Sween Date: Tue, 15 Mar 2011 15:27:04 -0400 Subject: [PATCH] Fils RSS pour toutes les briques --- auf_savoirs_en_partage/chercheurs/models.py | 11 +- auf_savoirs_en_partage/savoirs/models.py | 70 ++++++--- auf_savoirs_en_partage/savoirs/rss.py | 175 +++++++++++++++++----- auf_savoirs_en_partage/scripts/sphinx.conf.py.in | 4 +- auf_savoirs_en_partage/sitotheque/models.py | 12 ++ auf_savoirs_en_partage/urls.py | 23 ++- buildout.cfg | 1 + 7 files changed, 222 insertions(+), 74 deletions(-) diff --git a/auf_savoirs_en_partage/chercheurs/models.py b/auf_savoirs_en_partage/chercheurs/models.py index 8c1380a..ddc4414 100644 --- a/auf_savoirs_en_partage/chercheurs/models.py +++ b/auf_savoirs_en_partage/chercheurs/models.py @@ -70,6 +70,9 @@ class ChercheurQuerySet(SEPQuerySet): def filter_expert(self): return self.exclude(expertises=None) + def filter_date_modification(self, min=None, max=None): + return self._filter_date('date_modification', min=min, max=max) + def order_by_nom(self, direction=''): return self.order_by(direction + 'nom', direction + 'prenom', '-date_modification') @@ -113,6 +116,9 @@ class ChercheurSphinxQuerySet(SEPSphinxQuerySet): def filter_expert(self): return self.filter(expert=True) + def filter_date_modification(self, min=None, max=None): + return self._filter_date(self, 'date_modification', min=min, max=max) + def order_by_nom(self, direction=''): return self.order_by(direction + 'nom_complet', '-date_modification') @@ -152,6 +158,9 @@ class ChercheurManager(SEPManager): def filter_expert(self): return self.get_query_set().filter_expert() + def filter_date_modification(self, min=None, max=None): + return self.get_query_set().filter_date_modification(min=min, max=max) + def order_by_nom(self, direction=''): return self.get_query_set().order_by_nom(self, direction=direction) @@ -266,7 +275,7 @@ class Chercheur(Personne): @property def etablissement_display(self): - return self.nom_etablissement + ', ' + self.pays + return (self.nom_etablissement or '') + (', ' + self.pays.nom if self.pays else '') @property def pays(self): diff --git a/auf_savoirs_en_partage/savoirs/models.py b/auf_savoirs_en_partage/savoirs/models.py index 2e02294..fa71313 100644 --- a/auf_savoirs_en_partage/savoirs/models.py +++ b/auf_savoirs_en_partage/savoirs/models.py @@ -42,7 +42,16 @@ class RandomQuerySetMixin(object): return [self[p] for p in positions] class SEPQuerySet(models.query.QuerySet, RandomQuerySetMixin): - pass + + def _filter_date(self, field, min=None, max=None): + """Limite les résultats à ceux dont le champ ``field`` tombe entre + les dates ``min`` et ``max``.""" + qs = self + if min: + qs = qs.filter(**{field + '__gte': min}) + if max: + qs = qs.filter(**{field + '__lte': max}) + return qs class SEPSphinxQuerySet(SphinxQuerySet, RandomQuerySetMixin): """Fonctionnalités communes aux query sets de Sphinx.""" @@ -79,6 +88,16 @@ class SEPSphinxQuerySet(SphinxQuerySet, RandomQuerySetMixin): tous les champs.""" return self.search('"%s"' % region.nom) + def _filter_date(self, field, min=None, max=None): + """Limite les résultats à ceux dont le champ ``field`` tombe entre + les dates ``min`` et ``max``.""" + qs = self + if min: + qs = qs.filter(**{field + '__gte': min.toordinal()+365}) + if max: + qs = qs.filter(**{field + '__lte': max.toordinal()+365}) + return qs + def _get_sphinx_results(self): try: return SphinxQuerySet._get_sphinx_results(self) @@ -163,12 +182,7 @@ class SourceActualite(models.Model): class ActualiteQuerySet(SEPQuerySet): def filter_date(self, min=None, max=None): - qs = self - if min: - qs = qs.filter(date__gte=min) - if max: - qs = qs.filter(date__lte=max) - return qs + return self._filter_date('date', min=min, max=max) def filter_type(self, type): return self.filter(source__type=type) @@ -180,13 +194,8 @@ class ActualiteSphinxQuerySet(SEPSphinxQuerySet): weights=dict(titre=3)) def filter_date(self, min=None, max=None): - qs = self - if min: - qs = qs.filter(date__gte=min.toordinal()+365) - if max: - qs = qs.filter(date__lte=max.toordinal()+365) - return qs - + return self._filter_date('date', min=min, max=max) + TYPE_CODES = {'actu': 1, 'appels': 2} def filter_type(self, type): return self.filter(type=self.TYPE_CODES[type]) @@ -227,6 +236,9 @@ class Actualite(models.Model): def __unicode__ (self): return "%s" % (self.titre) + def get_absolute_url(self): + return reverse('actualite', kwargs={'id': self.id}) + def assigner_disciplines(self, disciplines): self.disciplines.add(*disciplines) @@ -258,12 +270,7 @@ class EvenementSphinxQuerySet(SEPSphinxQuerySet): return self.add_to_query('@type "%s"' % type) def filter_debut(self, min=None, max=None): - qs = self - if min: - qs = qs.filter(debut__gte=min.toordinal()+365) - if max: - qs = qs.filter(debut__lte=max.toordinal()+365) - return qs + return self._filter_date('debut', min=min, max=max) class EvenementManager(SEPManager): @@ -338,9 +345,12 @@ class Evenement(models.Model): verbose_name = u'évènement' verbose_name_plural = u'évènements' - def __unicode__(self,): + def __unicode__(self): return "[%s] %s" % (self.uid, self.titre) + def get_absolute_url(self): + return reverse('evenement', kwargs={'id': self.id}) + def duration_display(self): delta = self.fin - self.debut minutes, seconds = divmod(delta.seconds, 60) @@ -489,18 +499,26 @@ class ListSet(models.Model): def __unicode__(self,): return self.name +class RecordQuerySet(SEPQuerySet): + + def filter_modified(self, min=None, max=None): + return self._filter_date(self, 'modified', min=min, max=max) + class RecordSphinxQuerySet(SEPSphinxQuerySet): def __init__(self, model=None): SEPSphinxQuerySet.__init__(self, model=model, index='savoirsenpartage_ressources', weights=dict(title=3)) + def filter_modified(self, min=None, max=None): + return self._filter_date(self, 'modified', min=min, max=max) + class RecordManager(SEPManager): def get_query_set(self): """Ne garder que les ressources validées et qui sont soit dans aucun listset ou au moins dans un listset validé.""" - qs = SEPQuerySet(self.model) + qs = RecordQuerySet(self.model) qs = qs.filter(validated=True) qs = qs.filter(Q(listsets__isnull=True) | Q(listsets__validated=True)) return qs.distinct() @@ -508,6 +526,9 @@ class RecordManager(SEPManager): def get_sphinx_query_set(self): return RecordSphinxQuerySet(self.model) + def filter_modified(self, min=None, max=None): + return self.get_query_set().filter_modified(min=min, max=max) + class Record(models.Model): #fonctionnement interne @@ -558,6 +579,9 @@ class Record(models.Model): def __unicode__(self): return "[%s] %s" % (self.server, self.title) + def get_absolute_url(self): + return reverse('ressource', kwargs={'id': self.id}) + def getServeurURL(self): """Retourne l'URL du serveur de provenance""" return RESOURCES[self.server]['url'] @@ -757,7 +781,7 @@ class RessourceSearch(Search): if not self.q: """Montrer les résultats les plus récents si on n'a pas fait une recherche par mots-clés.""" - results = results.order_by('-id') + results = results.order_by('-modified') return results.all() def url(self): diff --git a/auf_savoirs_en_partage/savoirs/rss.py b/auf_savoirs_en_partage/savoirs/rss.py index cc7889a..269efc5 100644 --- a/auf_savoirs_en_partage/savoirs/rss.py +++ b/auf_savoirs_en_partage/savoirs/rss.py @@ -1,52 +1,153 @@ # -*- encoding: utf-8 -*- -from datetime import datetime +from datetime import datetime, date, timedelta +from dateutil.parser import parse as parse_date +from dateutil.tz import tzlocal, tzutc + from django.core.urlresolvers import reverse -from django.contrib.syndication.feeds import Feed -from savoirs.models import Actualite, Evenement -from datetime import datetime, time +from django.contrib.syndication.views import Feed + +from chercheurs.forms import ChercheurSearchForm +from savoirs.forms import RessourceSearchForm, ActualiteSearchForm, EvenementSearchForm +from sitotheque.forms import SiteSearchForm + +class FilChercheurs(Feed): + title = "Savoirs en partage - chercheurs" + link = "/chercheurs/" + description = "Fiches de chercheurs mises à jour récemment sur Savoirs en partage" + + def get_object(self, request): + search_form = ChercheurSearchForm(request.GET) + return search_form.save(commit=False) + + def items(self, search): + min_date = date.today() - timedelta(days=30) + return search.run().order_by('-date_modification').filter_date_modification(min=min_date) + + def item_title(self, chercheur): + return unicode(chercheur) + + def item_description(self, chercheur): + return chercheur.etablissement_display + + def item_link(self, chercheur): + return reverse('chercheur', kwargs=dict(id=chercheur.id)) + + def item_pubdate(self, chercheur): + d = chercheur.date_modification + return datetime(d.year, d.month, d.day, tzinfo=tzlocal()) + +class FilRessources(Feed): + title = "Savoirs en partage - ressources" + link = "/ressources/" + description = "Ressources nouvellement disponibles sur Savoirs en partage" + + def get_object(self, request): + search_form = RessourceSearchForm(request.GET) + return search_form.save(commit=False) + + def items(self, search): + min_date = date.today() - timedelta(days=30) + return search.run().order_by('-modified').filter_modified(min=min_date) + + def item_title(self, ressource): + return ressource.title + + def item_description(self, ressource): + return ressource.description + + def item_author_name(self, ressource): + return ressource.creator + + def item_pubdate(self, ressource): + try: + modified = parse_date(ressource.modified) + except ValueError: + modified = datetime.now() + if modified.tzinfo is None: + modified.tzinfo = tzutc() + return modified + +class FilActualitesBase(Feed): + + def get_object(self, request): + search_form = ActualiteSearchForm(request.GET) + return search_form.save(commit=False) + + def items(self, search): + min_date = date.today() - timedelta(days=30) + return search.run().filter_date(min=min_date).order_by('-date') + + def item_title(self, actualite): + return actualite.titre + + def item_description(self, actualite): + return actualite.texte + + def item_author_name(self, actualite): + return actualite.source.nom + + def item_pubdate(self, actualite): + d = actualite.date + return datetime(d.year, d.month, d.day, tzinfo=tzutc()) + +class FilActualites(FilActualitesBase): + title = "Savoirs en partage - actualités" + link = "/actualites/" + description = "Actualités récentes sur Savoirs en partage" + + def items(self, search): + return FilActualitesBase.items(self, search).filter_type('actu') + +class FilAppels(FilActualitesBase): + title = "Savoirs en partage - appels d'offres" + link = "/appels/" + description = "Appels d'offres récents sur Savoirs en partage" -class FilActualite(Feed): - title = "Dernières actualités du portail des ressources scientifiques et pédagogiques de l'AUF" - link = '/' - description = "Agrégateur de ressources scientifiques et pédagogiques de l'AUF" - limitation = 10 - type = 'actu' + def items(self, search): + return FilActualitesBase.items(self, search).filter_type('appels') - title_template = "savoirs/rss_actualite_titre.html" - description_template = "savoirs/rss_actualite_description.html" +class FilEvenements(Feed): + title = "Savoirs en partage - agenda" + link = "/agenda/" + description = "Agenda Savoirs en partage" + description_template = 'savoirs/rss_evenement_description.html' - def items(self): - return Actualite.objects.filter(visible=True).filter_type(self.type).order_by('-date')[:self.limitation] + def get_object(self, request): + search_form = EvenementSearchForm(request.GET) + return search_form.save(commit=False) - def item_link(self, item): - return item.url + def items(self, search): + min_date = date.today() + max_date = date.today() + timedelta(days=30) + return search.run().filter_debut(min=min_date, max=max_date).order_by('-debut') - def item_pubdate(self,item): - return datetime.combine(item.date, time()) + def item_title(self, evenement): + return evenement.titre - def item_author_name(self,item): - if item.source: - return item.source.nom + def item_author_name(self, evenement): + return ' '.join([evenement.prenom, evenement.nom]) -class FilAppels(FilActualite): - type = 'appels' + def item_author_email(self, evenement): + return evenement.courriel -class FilEvenement(Feed): - title = "Calendrier des ressources scientifiques et pédagogiques de l'AUF" - link = '/' - description = "Evènements connexes aux ressources scientifiques et pédagogiques de l'AUF" +class FilSites(Feed): + title = "Savoirs en partage - sites" + link = "/sites/" + description = "Sites récemment ajoutés à Savoirs en partage" - title_template = "savoirs/rss_evenement_titre.html" - description_template = "savoirs/rss_evenement_description.html" + def get_object(self, request): + search_form = SiteSearchForm(request.GET) + return search_form.save(commit=False) - def items(self): - return Evenement.objects.filter(approuve=True, debut__gte=datetime.now()) + def items(self, search): + min_date = date.today() - timedelta(days=365) + return search.run().filter_date_maj(min=min_date) - def item_link(self, item): - return reverse('savoirs.views.evenement', args=[item.id]) + def item_title(self, site): + return site.titre - def item_pubdate(self,item): - return item.debut + def item_description(self, site): + return site.description - def item_author_name(self,item): - return "" + def item_author_name(self, site): + return site.auteur diff --git a/auf_savoirs_en_partage/scripts/sphinx.conf.py.in b/auf_savoirs_en_partage/scripts/sphinx.conf.py.in index 612a815..5f72808 100644 --- a/auf_savoirs_en_partage/scripts/sphinx.conf.py.in +++ b/auf_savoirs_en_partage/scripts/sphinx.conf.py.in @@ -92,6 +92,7 @@ emit_source('savoirsenpartage_ressources', r.contributor AS contributor, r.subject AS subject, r.publisher AS publisher, + TO_DAYS(r.modified) AS modified, GROUP_CONCAT(DISTINCT d.nom_discipline) AS disciplines, GROUP_CONCAT(DISTINCT d.id_discipline) AS discipline_ids, GROUP_CONCAT(DISTINCT p.nom) AS pays, @@ -109,7 +110,8 @@ emit_source('savoirsenpartage_ressources', WHERE r.validated AND (l.spec IS NULL OR l.validated) GROUP BY r.id''', sql_query_info='SELECT * from savoirs_record WHERE id=$id', - sql_attr_multi=['discipline_ids', 'region_ids'] + sql_attr_multi=['discipline_ids', 'region_ids'], + sql_attr_uint=['modified'] ) emit_source('savoirsenpartage_actualites', diff --git a/auf_savoirs_en_partage/sitotheque/models.py b/auf_savoirs_en_partage/sitotheque/models.py index 196b8f2..801de35 100644 --- a/auf_savoirs_en_partage/sitotheque/models.py +++ b/auf_savoirs_en_partage/sitotheque/models.py @@ -25,11 +25,17 @@ class SiteQuerySet(SEPQuerySet): def filter_pays(self, pays): return self.filter(pays=pays) + def filter_date_maj(self, min=None, max=None): + return self._filter_date('date_maj', min=min, max=max) + class SiteSphinxQuerySet(SEPSphinxQuerySet): def __init__(self, model=None): SEPSphinxQuerySet.__init__(self, model=model, index='savoirsenpartage_sites', weights=dict(titre=3)) + def filter_date_maj(self, min=None, max=None): + return self._filter_date('date_maj', min=min, max=max) + def filter_pays(self, pays): return self.filter(pays_ids=pays.id) @@ -44,6 +50,9 @@ class SiteManager(SEPManager): def filter_pays(self, pays): return self.get_query_set().filter_pays(pays) + def filter_date_maj(self, min=None, max=None): + return self.get_query_set().filter_date_maj(self, min=min, max=max) + class Site(models.Model): """Fiche d'info d'un site web""" url = models.URLField(verify_exists=False) # dc:identifier (dc:source?) @@ -78,6 +87,9 @@ class Site(models.Model): def __unicode__(self): return "%s" % (self.titre) + def get_absolute_url(self): + return reverse('site', kwargs={'id': self.id}) + def type_display(self): for t in TYPE_SITE_CHOICES: if self.type == t[0]: diff --git a/auf_savoirs_en_partage/urls.py b/auf_savoirs_en_partage/urls.py index 6c79f7e..9e841bc 100644 --- a/auf_savoirs_en_partage/urls.py +++ b/auf_savoirs_en_partage/urls.py @@ -3,19 +3,13 @@ from django.conf.urls.defaults import patterns, include, handler500, handler404, url from django.conf import settings from django.contrib import admin -from savoirs.rss import FilActualite, FilEvenement, FilAppels +from savoirs.rss import FilChercheurs, FilRessources, FilActualites, FilAppels, FilEvenements, FilSites admin.autodiscover() handler500 = "views.page_500" handler404 = "views.page_404" -site_feeds = {'actualites': FilActualite, - 'agenda': FilEvenement, - 'appels': FilAppels - } - - # Les URLs suivantes peuvent être préfixées de la discipline et/ou la # région. Nous les regroupons donc dans un module qu'on incluera plus bas. sep_patterns = patterns( @@ -35,7 +29,7 @@ urlpatterns = sep_patterns + patterns( # agenda (r'^agenda/$', 'savoirs.views.evenement_index', {}, 'agenda'), - (r'^agenda/evenements/(?P\d+)/$', 'savoirs.views.evenement'), + (r'^agenda/evenements/(?P\d+)/$', 'savoirs.views.evenement', {}, 'evenement'), (r'^agenda/evenements/moderer/$', 'savoirs.views.evenement_moderation'), (r'^agenda/evenements/moderer/(.+)/accepter/$', 'savoirs.views.evenement_accepter'), (r'^agenda/evenements/moderer/(.+)/refuser/$', 'savoirs.views.evenement_refuser'), @@ -52,7 +46,7 @@ urlpatterns = sep_patterns + patterns( # ressources (r'^ressources/$', 'savoirs.views.ressource_index', {}, 'ressources'), - (r'^ressources/(?P\d+)/$', 'savoirs.views.ressource_retrieve'), + (r'^ressources/(?P\d+)/$', 'savoirs.views.ressource_retrieve', {}, 'ressource'), # actualités (r'^actualites/$', 'savoirs.views.actualite_index', {}, 'actualites'), @@ -61,7 +55,7 @@ urlpatterns = sep_patterns + patterns( # sites (r'^sites/$', 'sitotheque.views.index', {}, 'sites'), - (r'^sites/(?P\d+)/$', 'sitotheque.views.retrieve'), + (r'^sites/(?P\d+)/$', 'sitotheque.views.retrieve', {}, 'site'), (r'^sites/google.xml$', 'sitotheque.views.config_google'), # sites AUF @@ -69,7 +63,7 @@ urlpatterns = sep_patterns + patterns( # chercheurs (r'^chercheurs/$', 'chercheurs.views.index', {}, 'chercheurs'), - (r'^chercheurs/(?P\d+)/$', 'chercheurs.views.retrieve'), + (r'^chercheurs/(?P\d+)/$', 'chercheurs.views.retrieve', {}, 'chercheur'), (r'^chercheurs/inscription/$', 'chercheurs.views.inscription', {}, 'inscription'), (r'^chercheurs/inscription_faite/$', 'django.views.generic.simple.direct_to_template', dict( template='chercheurs/inscription_faite.html' @@ -130,7 +124,12 @@ urlpatterns = sep_patterns + patterns( (r'^stats/$', 'savoirs.admin_views.stats', {}, 'stats'), # rss - (r'^rss/(.*)/$', 'django.contrib.syndication.views.feed', {'feed_dict':site_feeds}), + (r'^rss/chercheurs/$', FilChercheurs()), + (r'^rss/ressources/$', FilRessources()), + (r'^rss/actualites/$', FilActualites()), + (r'^rss/appels/$', FilAppels()), + (r'^rss/agenda/$', FilEvenements()), + (r'^rss/sites/$', FilSites()), (r'^json/get/$', 'savoirs.views.json_get'), (r'^json/set/$', 'savoirs.views.json_set'), diff --git a/buildout.cfg b/buildout.cfg index 1c16947..bc94421 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -34,6 +34,7 @@ eggs = auf_references_client==0.4.9 PyYAML==3.09 South==0.7.3 django_exportateur==1.0 + python-dateutil==1.5 auf.django.admingroup MySQL-python simplejson -- 1.7.10.4