Chris Johnston has proposed merging lp:~mhall119/loco-directory/loco-feeds into
lp:loco-directory.
Requested reviews:
loco-directory-dev (loco-directory-dev)
For more details, see:
https://code.launchpad.net/~mhall119/loco-directory/loco-feeds/+merge/69856
--
https://code.launchpad.net/~mhall119/loco-directory/loco-feeds/+merge/69856
Your team loco-directory-dev is requested to review the proposed merge of
lp:~mhall119/loco-directory/loco-feeds into lp:loco-directory.
=== added directory 'loco_directory/articles'
=== added file 'loco_directory/articles/__init__.py'
=== added file 'loco_directory/articles/admin.py'
--- loco_directory/articles/admin.py 1970-01-01 00:00:00 +0000
+++ loco_directory/articles/admin.py 2011-07-29 20:46:30 +0000
@@ -0,0 +1,13 @@
+from articles.models import Feed, Article
+from django.contrib import admin
+
+class FeedAdmin(admin.ModelAdmin):
+ list_display = ('title', 'team', 'user', 'url', 'last_updated')
+ list_filter = ('team',)
+admin.site.register(Feed, FeedAdmin)
+
+class ArticleAdmin(admin.ModelAdmin):
+ list_display = ('title', 'source', 'published', 'imported')
+ list_filter = ('source',)
+admin.site.register(Article, ArticleAdmin)
+
=== added directory 'loco_directory/articles/management'
=== added file 'loco_directory/articles/management/__init__.py'
=== added directory 'loco_directory/articles/management/commands'
=== added file 'loco_directory/articles/management/commands/__init__.py'
=== added file 'loco_directory/articles/management/commands/refresh.py'
--- loco_directory/articles/management/commands/refresh.py 1970-01-01 00:00:00 +0000
+++ loco_directory/articles/management/commands/refresh.py 2011-07-29 20:46:30 +0000
@@ -0,0 +1,56 @@
+'''
+Updates all Feeds
+'''
+
+import os
+import datetime
+import time
+
+from django.core.management.base import NoArgsCommand
+
+from articles.models import Feed
+
+REFRESH_LOCK = '/var/lock/loco-directory/refresh'
+class Command(NoArgsCommand):
+ help = "Updates all Feeds in the database."
+
+ def handle_noargs(self, **options):
+ verbosity = int(options.get('verbosity', 1))
+
+ # obtain lock
+ if not os.path.exists(os.path.dirname(REFRESH_LOCK)):
+ if verbosity >= 2:
+ print "Creating lock file directory"
+ os.mkdir(os.path.dirname(REFRESH_LOCK))
+
+ if os.path.exists(REFRESH_LOCK):
+ if verbosity >= 2:
+ print "Lock file exists, exiting"
+ return
+ else:
+ if verbosity >= 2:
+ print "Creating lock file"
+ lock = open(REFRESH_LOCK, 'w')
+ if not lock:
+ if verbosity >= 1:
+ print "Failed to create the lock file"
+ raise Exception("Failed to open lock file")
+ lock.write('%s\n' % os.getpid())
+ lock.close()
+
+ try:
+ if verbosity >= 2:
+ print "Processing feeds"
+ # Process feeds
+ for feed in Feed.objects.filter(active=True):
+ if verbosity >= 2:
+ print "[%s] Updating: %s" % (datetime.datetime.now(), feed.title)
+ try:
+ feed.update()
+ except Exception, e:
+ print "Error reading %s: %s" % (feed.url, e)
+ except Exception, e:
+ print "Error refreshing feeds: %s" % e
+ finally:
+ # release lock
+ os.remove(REFRESH_LOCK)
=== added directory 'loco_directory/articles/migrations'
=== added file 'loco_directory/articles/migrations/0001_initial.py'
--- loco_directory/articles/migrations/0001_initial.py 1970-01-01 00:00:00 +0000
+++ loco_directory/articles/migrations/0001_initial.py 2011-07-29 20:46:30 +0000
@@ -0,0 +1,164 @@
+
+from south.db import db
+from django.db import models
+from articles.models import *
+
+class Migration:
+
+ def forwards(self, orm):
+
+ # Adding model 'Feed'
+ db.create_table('articles_feed', (
+ ('id', orm['articles.Feed:id']),
+ ('user', orm['articles.Feed:user']),
+ ('team', orm['articles.Feed:team']),
+ ('url', orm['articles.Feed:url']),
+ ('title', orm['articles.Feed:title']),
+ ('last_updated', orm['articles.Feed:last_updated']),
+ ('active', orm['articles.Feed:active']),
+ ))
+ db.send_create_signal('articles', ['Feed'])
+
+ # Adding model 'Article'
+ db.create_table('articles_article', (
+ ('id', orm['articles.Article:id']),
+ ('source', orm['articles.Article:source']),
+ ('uid', orm['articles.Article:uid']),
+ ('author', orm['articles.Article:author']),
+ ('published', orm['articles.Article:published']),
+ ('imported', orm['articles.Article:imported']),
+ ('link', orm['articles.Article:link']),
+ ('title', orm['articles.Article:title']),
+ ('snippet', orm['articles.Article:snippet']),
+ ))
+ db.send_create_signal('articles', ['Article'])
+
+
+
+ def backwards(self, orm):
+
+ # Deleting model 'Feed'
+ db.delete_table('articles_feed')
+
+ # Deleting model 'Article'
+ db.delete_table('articles_article')
+
+
+
+ models = {
+ 'articles.article': {
+ 'author': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'imported': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'link': ('django.db.models.fields.URLField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
+ 'published': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+ 'snippet': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True', 'blank': 'True'}),
+ 'source': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['articles.Feed']", 'null': 'True', 'blank': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
+ 'uid': ('django.db.models.fields.CharField', [], {'max_length': '256'})
+ },
+ 'articles.feed': {
+ 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'last_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(1970, 1, 1, 0, 0)'}),
+ 'team': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['teams.Team']", 'null': 'True', 'blank': 'True'}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
+ 'url': ('django.db.models.fields.URLField', [], {'max_length': '200'}),
+ 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['userprofiles.UserProfile']", 'null': 'True', 'blank': 'True'})
+ },
+ 'auth.group': {
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+ 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'})
+ },
+ 'auth.permission': {
+ 'Meta': {'unique_together': "(('content_type', 'codename'),)"},
+ 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+ },
+ 'auth.user': {
+ 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+ 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+ 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+ 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+ 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'blank': 'True'}),
+ 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+ },
+ 'contenttypes.contenttype': {
+ 'Meta': {'unique_together': "(('app_label', 'model'),)", 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'teams.continent': {
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.TextField', [], {'max_length': '50'})
+ },
+ 'teams.country': {
+ 'continents': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['teams.Continent']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.TextField', [], {'max_length': '100'})
+ },
+ 'teams.language': {
+ 'code': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '150', 'null': 'True'})
+ },
+ 'teams.team': {
+ 'Meta': {'db_table': "'teams'"},
+ 'active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+ 'admin_profiles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['userprofiles.UserProfile']"}),
+ 'approved': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+ 'approved_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+ 'city': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
+ 'contact_profiles': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['userprofiles.UserProfile']"}),
+ 'countries': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['teams.Country']"}),
+ 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
+ 'expires_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+ 'flickr_id': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}),
+ 'forum_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'irc_chan': ('django.db.models.fields.CharField', [], {'max_length': '25', 'null': 'True', 'blank': 'True'}),
+ 'languages': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['teams.Language']"}),
+ 'lp_name': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True'}),
+ 'microbloghashtag': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
+ 'ml_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'mugshot_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'null': 'True'}),
+ 'owner_profile': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'owner'", 'null': 'True', 'to': "orm['userprofiles.UserProfile']"}),
+ 'picasa_id': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}),
+ 'pixie_id': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}),
+ 'provides_support': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+ 'spr': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
+ 'web_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'wiki_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'})
+ },
+ 'userprofiles.userprofile': {
+ 'aim': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
+ 'blog': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+ 'facebook': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
+ 'flickr': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'identica': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
+ 'irc': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
+ 'mugshot': ('django.db.models.fields.URLField', [], {'max_length': '150', 'null': 'True', 'blank': 'True'}),
+ 'picasa': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
+ 'realname': ('django.db.models.fields.CharField', [], {'max_length': '150', 'blank': 'True'}),
+ 'twitter': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
+ 'tz': ('django.db.models.fields.CharField', [], {'default': "'UTC'", 'max_length': '32'}),
+ 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True'}),
+ 'xmpp': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'})
+ }
+ }
+
+ complete_apps = ['articles']
=== added file 'loco_directory/articles/migrations/__init__.py'
=== added file 'loco_directory/articles/models.py'
--- loco_directory/articles/models.py 1970-01-01 00:00:00 +0000
+++ loco_directory/articles/models.py 2011-07-29 20:46:30 +0000
@@ -0,0 +1,117 @@
+import feedparser
+import time
+import datetime
+import socket
+
+from django.db import models
+from django.contrib.auth.models import User
+from django.utils.html import strip_tags
+
+from teams.models import Team
+from userprofiles.models import UserProfile
+
+def create_feed(user, url):
+ data = feedparser.parse(url)
+ feed = Feed(
+ user=user,
+ url=url,
+ title=data.feed.title
+ )
+ feed.save()
+ feed.update(data)
+
+def get_entry_id(entry, feed):
+ if hasattr(entry, 'id'):
+ return entry.id
+
+ if hasattr(entry, 'link'):
+ return entry.link
+
+ return '%s:%s' % (feed.id, hash(entry.title))
+
+class Feed(models.Model):
+
+ class Meta:
+ ordering = ('title',)
+
+ user = models.ForeignKey(UserProfile, blank=True, null=True)
+ team = models.ForeignKey(Team, blank=True, null=True)
+ url = models.URLField()
+ title = models.CharField(max_length=128, null=True, blank=True)
+ last_updated = models.DateTimeField(null=False, blank=False, default=datetime.datetime(1970, 1, 1))
+
+ active = models.BooleanField(default=True)
+
+ def __unicode__(self):
+ return self.title
+
+ def update(self, data=None, first_load=False):
+ if not data:
+ old_timeout = socket.getdefaulttimeout()
+ socket.setdefaulttimeout(20)
+ data = feedparser.parse(self.url, modified=self.last_updated.utctimetuple())
+ socket.setdefaulttimeout(old_timeout)
+
+ if not data or not hasattr(data, 'entries') or len(data.entries) == 0:
+ # No new entries for this feed
+ return
+
+ now = datetime.datetime.now()
+ existing_articles = set(list(Article.objects.filter(source=self).values_list('uid', flat=True)))
+ returned_articles = set([])
+
+ if self.title is None or self.title == '':
+ self.title = data.feed.title
+ self.last_updated = datetime.datetime.fromtimestamp(time.mktime(getattr(data, 'modified', time.gmtime())))
+ self.save()
+
+ for entry in data.entries:
+ entry_id = get_entry_id(entry, self)
+ if Article.objects.filter(source=self, uid=entry_id).count() > 0:
+ returned_articles.add(entry_id)
+ else:
+ pub_date = datetime.datetime.fromtimestamp(time.mktime(entry.get('date_parsed', time.gmtime())))
+ # On first load, we want to mark the import as the published date
+ # otherwise they all go to the top of the list
+ if first_load:
+ imp_date = min(now, pub_date)
+ else:
+ imp_date = now
+
+ if hasattr(entry, 'content'):
+ snippet = entry.content[0].value
+ else:
+ snippet = entry.get('description', '')
+
+ article = Article(
+ source=self,
+ uid=entry_id,
+ author=entry.get('author', '')[:128],
+ published=pub_date,
+ imported=imp_date,
+ title=entry.get('title', '')[:128],
+ link=entry.get('link', None),
+ snippet=strip_tags(snippet)[:512],
+ )
+ article.save()
+
+ obsolete_articles = existing_articles - returned_articles
+ Article.objects.filter(source=self, uid__in=obsolete_articles).delete()
+
+class Article(models.Model):
+
+ class Meta:
+ ordering = ['-imported', '-published',]
+
+ source = models.ForeignKey(Feed, null=True, blank=True)
+ uid = models.CharField(max_length=256)
+ author = models.CharField(max_length=128, null=True, blank=True)
+ published = models.DateTimeField(null=True, blank=True)
+ imported = models.DateTimeField(null=True, blank=True)
+ link = models.URLField(max_length=1024, null=True, blank=True)
+ title = models.CharField(max_length=128, null=True, blank=True)
+ snippet = models.CharField(max_length=512, null=True, blank=True)
+
+ def __unicode__(self):
+ return '%s (%s)' % (self.title, self.author)
+
=== added file 'loco_directory/articles/tests.py'
--- loco_directory/articles/tests.py 1970-01-01 00:00:00 +0000
+++ loco_directory/articles/tests.py 2011-07-29 20:46:30 +0000
@@ -0,0 +1,23 @@
+"""
+This file demonstrates two different styles of tests (one doctest and one
+unittest). These will both pass when you run "manage.py test".
+
+Replace these with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+class SimpleTest(TestCase):
+ def test_basic_addition(self):
+ """
+ Tests that 1 + 1 always equals 2.
+ """
+ self.failUnlessEqual(1 + 1, 2)
+
+__test__ = {"doctest": """
+Another way to test that 1 + 1 is equal to 2.
+
+>>> 1 + 1 == 2
+True
+"""}
+
=== added file 'loco_directory/articles/views.py'
--- loco_directory/articles/views.py 1970-01-01 00:00:00 +0000
+++ loco_directory/articles/views.py 2011-07-29 20:46:30 +0000
@@ -0,0 +1,2 @@
+# Create your views here.
+
=== modified file 'loco_directory/common/views.py'
--- loco_directory/common/views.py 2011-02-18 03:16:32 +0000
+++ loco_directory/common/views.py 2011-07-29 20:46:30 +0000
@@ -9,12 +9,17 @@
def index(request):
from events.models import GlobalEvent, TeamEvent
from meetings.models import TeamMeeting
+ from articles.models import Article
team_event_count = TeamEvent.objects.next_events().count()
team_meeting_count = TeamMeeting.objects.next_meetings().count()
global_event_list = GlobalEvent.objects.next_events()[:5]
+ articles = Article.objects.all()[:5]
+
context = {'team_event_count': team_event_count,
'team_meeting_count': team_meeting_count,
- 'global_event_list': global_event_list,}
+ 'global_event_list': global_event_list,
+ 'articles': articles,
+ }
return render_to_response('index.html', context,
RequestContext(request))
=== modified file 'loco_directory/settings.py'
--- loco_directory/settings.py 2011-06-28 14:00:44 +0000
+++ loco_directory/settings.py 2011-07-29 20:46:30 +0000
@@ -134,6 +134,7 @@
'venues',
'events',
'meetings',
+ 'articles',
'userprofiles',
'django_openid_auth',
'south',
=== modified file 'loco_directory/teams/management/commands/update.py'
--- loco_directory/teams/management/commands/update.py 2010-09-11 15:12:47 +0000
+++ loco_directory/teams/management/commands/update.py 2011-07-29 20:46:30 +0000
@@ -14,4 +14,5 @@
run_job("update-languages", datetime.timedelta(days=1))
run_job("lpupdate", datetime.timedelta(minutes=20))
run_job("update-profiles", datetime.timedelta(days=1))
+ run_job("refresh", datetime.timedelta(minutes=30))
=== modified file 'loco_directory/templates/index.html'
--- loco_directory/templates/index.html 2011-03-18 15:06:54 +0000
+++ loco_directory/templates/index.html 2011-07-29 20:46:30 +0000
@@ -56,6 +56,7 @@
<hr class="divide" />
<br />
+<!--
<article class="minor-content">
<h2><a title="{% trans "Show all Events" %}" href="{% url event-list %}">{% trans "Upcoming Events" %}</a></h2>
<br />
@@ -84,6 +85,33 @@
<img src="/media/images/ical.png" title="{% trans "Team Meetings as ical" %}"/></a></h3>
{% endif %}
</article>
+-->
+
+<article class="minor-content alone">
+ {% for article in articles %}
+ <h2 class="title">
+ <a href="{{article.link}}" target="_blank">{{article.title}}</a>
+ </h2>
+ <div class="by-line">
+ <span class="team">
+ {% if article.source.team %}
+ <a href="{% url team-detail article.source.team %}" target="_blank">{{article.source.team.name}}</a>
+ {% endif %}
+ </span>
+ <span class="author">
+ {% if article.source.user %}
+ {% if article.source.team %} | {% endif %}
+ <a href="http://launchpad.net/~{{article.source.user.user.username}}" target="_blank">{{article.source.user}}</a>
+ {% endif %}
+ </span>
+ </div>
+ <div class="summary">
+ {{article.snippet|safe}} ... <a href="{{article.link}}" target="_blank">[{% trans 'Read More' %}]</a>
+ </div>
+
+ <br />
+ {% endfor %}
+</article>
<article class="minor-content alone">
<h2>{% trans "Microblogging" %} #locoteams</h2>
_______________________________________________
Mailing list: https://launchpad.net/~loco-directory-dev
Post to : [email protected]
Unsubscribe : https://launchpad.net/~loco-directory-dev
More help : https://help.launchpad.net/ListHelp