зеркало из https://github.com/mozilla/batucada.git
Starting to work on links in projects. Basically a project can add links and a scheduled task will pull in the RSS or Atom feed and add entries to the activity stream of that project. Work in progress
This commit is contained in:
Родитель
6d3b1e7e15
Коммит
3470f5d307
|
@ -0,0 +1,8 @@
|
|||
from django.db import models
|
||||
|
||||
from projects.models import Link
|
||||
|
||||
|
||||
class Entry(models.Model):
|
||||
link = models.ForeignKey(Link, related_name='feed_entries')
|
||||
processed_on = models.DateTimeField(auto_now_add=True)
|
|
@ -0,0 +1,19 @@
|
|||
import logging
|
||||
import feedparser
|
||||
|
||||
from celery.task.schedules import crontab
|
||||
from celery.decorators import periodic_task
|
||||
|
||||
from projects.models import Link
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@periodic_task(run_every=crontab())
|
||||
def load_project_feeds():
|
||||
links = Link.objects.all()
|
||||
for link in links:
|
||||
feed = feedparser.parse(link.url)
|
||||
log.debug(len(feed.entries))
|
||||
log.info("Running test task")
|
|
@ -4,25 +4,35 @@ from django import forms
|
|||
from django.utils.translation import ugettext as _
|
||||
|
||||
from messages.models import Message
|
||||
from projects.models import Project
|
||||
from projects.models import Project, Link
|
||||
|
||||
|
||||
class ProtectedProjectForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ProtectedProjectForm, self).__init__(*args,**kwargs)
|
||||
|
||||
super(ProtectedProjectForm, self).__init__(*args, **kwargs)
|
||||
protected = getattr(self.Meta, 'protected')
|
||||
project = kwargs.get('instance', None)
|
||||
|
||||
|
||||
if not project or not project.featured:
|
||||
for field in protected:
|
||||
self.fields.pop(field)
|
||||
|
||||
|
||||
class ProjectForm(ProtectedProjectForm):
|
||||
class Meta:
|
||||
model = Project
|
||||
exclude = ('created_by', 'slug', 'featured')
|
||||
protected = ('template', 'css')
|
||||
|
||||
|
||||
class ProjectLinkForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Link
|
||||
widgets = {
|
||||
'project': forms.HiddenInput(),
|
||||
}
|
||||
|
||||
|
||||
class ProjectContactUsersForm(forms.Form):
|
||||
"""
|
||||
A modified version of ``messages.forms.ComposeForm`` that enables
|
||||
|
@ -31,12 +41,12 @@ class ProjectContactUsersForm(forms.Form):
|
|||
"""
|
||||
project = forms.IntegerField(
|
||||
required=True,
|
||||
widget=forms.HiddenInput()
|
||||
widget=forms.HiddenInput(),
|
||||
)
|
||||
subject = forms.CharField(label=_(u'Subject'))
|
||||
body = forms.CharField(
|
||||
label=_(u'Body'),
|
||||
widget=forms.Textarea(attrs={'rows': '12', 'cols': '55'})
|
||||
widget=forms.Textarea(attrs={'rows': '12', 'cols': '55'}),
|
||||
)
|
||||
|
||||
def save(self, sender, parent_msg=None):
|
||||
|
@ -44,17 +54,18 @@ class ProjectContactUsersForm(forms.Form):
|
|||
try:
|
||||
project = Project.objects.get(id=int(project))
|
||||
except Project.DoesNotExist:
|
||||
raise forms.ValidationError(_(u'Hmm, that does not look like a valid project'))
|
||||
raise forms.ValidationError(
|
||||
_(u'Hmm, that does not look like a valid project'))
|
||||
recipients = project.followers()
|
||||
subject = self.cleaned_data['subject']
|
||||
body = self.cleaned_data['body']
|
||||
message_list = []
|
||||
for r in recipients:
|
||||
msg = Message(
|
||||
sender = sender,
|
||||
recipient = r,
|
||||
subject = subject,
|
||||
body = body,
|
||||
sender=sender,
|
||||
recipient=r,
|
||||
subject=subject,
|
||||
body=body,
|
||||
)
|
||||
if parent_msg is not None:
|
||||
msg.parent_msg = parent_msg
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
# encoding: utf-8
|
||||
import datetime
|
||||
from south.db import db
|
||||
from south.v2 import SchemaMigration
|
||||
from django.db import models
|
||||
|
||||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
|
||||
# Adding field 'Link.feed_url'
|
||||
db.add_column('projects_link', 'feed_url', self.gf('django.db.models.fields.URLField')(default='', max_length=200), keep_default=False)
|
||||
|
||||
# Adding unique constraint on 'Link', fields ['project', 'url']
|
||||
db.create_unique('projects_link', ['project_id', 'url'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
|
||||
# Removing unique constraint on 'Link', fields ['project', 'url']
|
||||
db.delete_unique('projects_link', ['project_id', 'url'])
|
||||
|
||||
# Deleting field 'Link.feed_url'
|
||||
db.delete_column('projects_link', 'feed_url')
|
||||
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': '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']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'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': {
|
||||
'Meta': {'object_name': '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']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'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']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', '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'})
|
||||
},
|
||||
'projects.link': {
|
||||
'Meta': {'unique_together': "(('project', 'url'),)", 'object_name': 'Link'},
|
||||
'feed_url': ('django.db.models.fields.URLField', [], {'default': "''", 'max_length': '200'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['projects.Project']"}),
|
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '250'}),
|
||||
'url': ('django.db.models.fields.URLField', [], {'max_length': '200'})
|
||||
},
|
||||
'projects.project': {
|
||||
'Meta': {'object_name': 'Project'},
|
||||
'call_to_action': ('django.db.models.fields.TextField', [], {}),
|
||||
'created_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects'", 'to': "orm['auth.User']"}),
|
||||
'css': ('django.db.models.fields.TextField', [], {}),
|
||||
'description': ('django.db.models.fields.TextField', [], {}),
|
||||
'featured': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}),
|
||||
'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'}),
|
||||
'template': ('django.db.models.fields.TextField', [], {})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['projects']
|
|
@ -1,32 +1,35 @@
|
|||
import urllib
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import models
|
||||
from django.db.models.signals import post_save
|
||||
from django.template.defaultfilters import slugify
|
||||
|
||||
from BeautifulSoup import BeautifulSoup
|
||||
|
||||
from relationships.models import followers
|
||||
|
||||
|
||||
class Project(models.Model):
|
||||
"""Placeholder model for projects."""
|
||||
object_type = 'http://drumbeat.org/activity/schema/1.0/project'
|
||||
generalized_object_type = 'http://activitystrea.ms/schema/1.0/group'
|
||||
|
||||
name = models.CharField(max_length=100, unique=True)
|
||||
slug = models.SlugField(unique=True)
|
||||
description = models.TextField()
|
||||
call_to_action = models.TextField()
|
||||
created_by = models.ForeignKey(User, related_name='projects')
|
||||
|
||||
featured = models.BooleanField()
|
||||
template = models.TextField()
|
||||
css = models.TextField()
|
||||
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
@models.permalink
|
||||
def get_absolute_url(self):
|
||||
return ('projects_show', (), {
|
||||
'slug': self.slug
|
||||
'slug': self.slug,
|
||||
})
|
||||
|
||||
def save(self):
|
||||
|
@ -44,18 +47,55 @@ class Project(models.Model):
|
|||
|
||||
Project.followers = followers
|
||||
|
||||
|
||||
class Link(models.Model):
|
||||
title = models.CharField(max_length=250)
|
||||
url = models.URLField()
|
||||
project = models.ForeignKey(Project)
|
||||
|
||||
feed_url = models.URLField(editable=False, default='')
|
||||
|
||||
class Meta:
|
||||
unique_together = ('project', 'url',)
|
||||
|
||||
def get_syndication_url(self):
|
||||
"""
|
||||
Parse the contents of this link and return the first Atom
|
||||
or RSS feed URI we find.
|
||||
@TODO - Account for cases where multiple rel="alternate"
|
||||
link elements are found in the document.
|
||||
"""
|
||||
contents = urllib.urlopen(self.url).read()
|
||||
soup = BeautifulSoup(contents)
|
||||
links = soup.head.findAll('link')
|
||||
|
||||
# BeautifulSoup instances are not actually dictionaries, so
|
||||
# we can't use the more proper 'key in dict' syntax and
|
||||
# must instead use the deprecated 'has_key()' method.
|
||||
alternate = [link for link in links
|
||||
if link.has_key('rel') and link['rel'] == 'alternate']
|
||||
atom = [link['href'] for link in alternate
|
||||
if (link.has_key('href') and link.has_key('type')
|
||||
and link['type'] == 'application/atom+xml')]
|
||||
|
||||
# we prefer atom to rss
|
||||
if atom:
|
||||
return atom[0]
|
||||
rss = [link['href'] for link in links
|
||||
if (link.has_key('href') and link.has_key('type')
|
||||
and link['type'] == 'application/rss+xml')]
|
||||
if rss:
|
||||
return rss[0]
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def project_creation_handler(sender, **kwargs):
|
||||
project = kwargs.get('instance', None)
|
||||
created = kwargs.get('created', False)
|
||||
|
||||
if not created or not isinstance(project, Project):
|
||||
return
|
||||
|
||||
|
||||
try:
|
||||
import activity
|
||||
activity.send(project.created_by, 'create', project)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from django.conf.urls.defaults import patterns, url
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^gallery/$', 'projects.views.gallery',
|
||||
url(r'^list/$', 'projects.views.list',
|
||||
name='projects_gallery'),
|
||||
url(r'^create/$', 'projects.views.create',
|
||||
name='projects_create'),
|
||||
|
@ -14,4 +14,6 @@ urlpatterns = patterns('',
|
|||
name='projects_contact_followers'),
|
||||
url(r'^(?P<slug>[\w-]+)/style.css$', 'projects.views.featured_css',
|
||||
name='projects_featured_css'),
|
||||
url(r'^(?P<slug>[\w-]+)/link/create/$', 'projects.views.link_create',
|
||||
name='projects_link_create'),
|
||||
)
|
||||
|
|
|
@ -1,14 +1,21 @@
|
|||
import urllib
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.http import HttpResponseForbidden
|
||||
from django.shortcuts import render_to_response, get_object_or_404
|
||||
from django.template import RequestContext, Context, Template
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from BeautifulSoup import BeautifulSoup
|
||||
|
||||
from projects.models import Project
|
||||
from projects.forms import ProjectForm, ProjectContactUsersForm
|
||||
from projects.forms import ProjectLinkForm
|
||||
|
||||
|
||||
def show(request, slug):
|
||||
project = get_object_or_404(Project, slug=slug)
|
||||
|
@ -30,6 +37,7 @@ def show(request, slug):
|
|||
return render_to_response('projects/featured.html', context,
|
||||
context_instance=RequestContext(request))
|
||||
|
||||
|
||||
@login_required
|
||||
def edit(request, slug):
|
||||
project = get_object_or_404(Project, slug=slug)
|
||||
|
@ -44,17 +52,19 @@ def edit(request, slug):
|
|||
reverse('projects_show', kwargs=dict(slug=project.slug)))
|
||||
else:
|
||||
form = ProjectForm(instance=project)
|
||||
|
||||
|
||||
return render_to_response('projects/edit.html', {
|
||||
'form': form,
|
||||
'project': project,
|
||||
}, context_instance=RequestContext(request))
|
||||
|
||||
def gallery(request):
|
||||
|
||||
def list(request):
|
||||
return render_to_response('projects/gallery.html', {
|
||||
'projects': Project.objects.all()
|
||||
'projects': Project.objects.all(),
|
||||
}, context_instance=RequestContext(request))
|
||||
|
||||
|
||||
@login_required
|
||||
def create(request):
|
||||
if request.method == 'POST':
|
||||
|
@ -64,19 +74,20 @@ def create(request):
|
|||
project.created_by = request.user
|
||||
project.save()
|
||||
return HttpResponseRedirect(reverse('projects_show', kwargs={
|
||||
'slug': project.slug
|
||||
'slug': project.slug,
|
||||
}))
|
||||
else:
|
||||
form = ProjectForm()
|
||||
return render_to_response('projects/create.html', {
|
||||
'form': form
|
||||
'form': form,
|
||||
}, context_instance=RequestContext(request))
|
||||
|
||||
|
||||
@login_required
|
||||
def contact_followers(request, slug):
|
||||
project = get_object_or_404(Project, slug=slug)
|
||||
if project.created_by != request.user:
|
||||
return HttpResponseForbidden
|
||||
return HttpResponseForbidden()
|
||||
if request.method == 'POST':
|
||||
form = ProjectContactUsersForm(request.POST)
|
||||
if form.is_valid():
|
||||
|
@ -84,7 +95,7 @@ def contact_followers(request, slug):
|
|||
messages.add_message(request, messages.INFO,
|
||||
_("Message successfully sent."))
|
||||
return HttpResponseRedirect(reverse('projects_show', kwargs={
|
||||
'slug': project.slug
|
||||
'slug': project.slug,
|
||||
}))
|
||||
else:
|
||||
form = ProjectContactUsersForm()
|
||||
|
@ -94,6 +105,31 @@ def contact_followers(request, slug):
|
|||
'project': project,
|
||||
}, context_instance=RequestContext(request))
|
||||
|
||||
|
||||
def featured_css(request, slug):
|
||||
project = get_object_or_404(Project, slug=slug)
|
||||
return HttpResponse(project.css, mimetype='text/css')
|
||||
|
||||
|
||||
def link_create(request, slug):
|
||||
project = get_object_or_404(Project, slug=slug)
|
||||
if project.created_by != request.user:
|
||||
return HttpResponseForbidden()
|
||||
form = ProjectLinkForm(initial=dict(project=project.pk))
|
||||
if request.method == 'POST':
|
||||
form = ProjectLinkForm(data=request.POST)
|
||||
if form.is_valid():
|
||||
messages.add_message(request, messages.INFO,
|
||||
_("Your link has been created"))
|
||||
link = form.save()
|
||||
feed_url = link.get_syndication_url()
|
||||
if feed_url:
|
||||
link.feed_url = feed_url
|
||||
link.save()
|
||||
return HttpResponseRedirect(reverse('projects_show', kwargs={
|
||||
'slug': project.slug,
|
||||
}))
|
||||
return render_to_response('projects/create_link.html', {
|
||||
'form': form,
|
||||
'project': project,
|
||||
}, context_instance=RequestContext(request))
|
||||
|
|
|
@ -5,6 +5,10 @@ pil
|
|||
django-messages
|
||||
south
|
||||
jogging
|
||||
feedparser
|
||||
Celery==2.1.3
|
||||
django-celery
|
||||
BeautifulSoup
|
||||
|
||||
-e git://github.com/jbalogh/django-nose#egg=django-nose
|
||||
-e git://github.com/clouserw/tower.git#egg=tower
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
|
||||
import os
|
||||
import logging
|
||||
import djcelery
|
||||
|
||||
djcelery.setup_loader()
|
||||
|
||||
# Make filepaths relative to settings.
|
||||
ROOT = os.path.dirname(os.path.abspath(__file__))
|
||||
|
@ -103,6 +106,9 @@ INSTALLED_APPS = (
|
|||
'django.contrib.admin',
|
||||
'django_nose',
|
||||
'django_openid_auth',
|
||||
'south',
|
||||
'jogging',
|
||||
'djcelery',
|
||||
'wellknown',
|
||||
'users',
|
||||
'profiles',
|
||||
|
@ -113,8 +119,7 @@ INSTALLED_APPS = (
|
|||
'projects',
|
||||
'statuses',
|
||||
'messages',
|
||||
'south',
|
||||
'jogging',
|
||||
'feeds',
|
||||
)
|
||||
|
||||
if DEBUG:
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
{% extends "base.html" %}
|
||||
{% load l10n_tags %}
|
||||
{% block title %}Add Link{% endblock %}
|
||||
{% block body %}
|
||||
<h3>{{ _('Add Link') }}</h3>
|
||||
<form action="{% locale_url projects_link_create slug=project.slug %}" method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<input type="submit" value="Create" />
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -10,9 +10,20 @@
|
|||
{% include "projects/_project_admin.html" %}
|
||||
{% include "projects/_followers.html" %}
|
||||
{% include "projects/_message_followers.html" %}
|
||||
<h4>{{ _('Links') }}</h4>
|
||||
<ul>
|
||||
{% for link in project.link_set.all %}
|
||||
<li><a href="{{ link.url }}">{{ link.title }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% if user == project.created_by %}
|
||||
<p>
|
||||
<a href="{% locale_url projects_edit slug=project.slug %}">Edit</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if user == project.created_by %}
|
||||
<p>
|
||||
<a href="{% locale_url projects_link_create slug=project.slug %}">Add Link</a>
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
Загрузка…
Ссылка в новой задаче