Skip to content

Commit

Permalink
Added the concept of "owned" feeds, and let users edit/delete their o…
Browse files Browse the repository at this point in the history
…wn feeds.
  • Loading branch information
jacobian committed Jan 31, 2011
1 parent b1db2b4 commit ce959eb
Show file tree
Hide file tree
Showing 12 changed files with 261 additions and 34 deletions.
1 change: 0 additions & 1 deletion TODO
@@ -1,2 +1 @@
* allow users to "own" feeds and self-edit
* allow users to "claim" legacy feeds (might do this by hand.)
1 change: 1 addition & 0 deletions django_website/aggregator/admin.py
Expand Up @@ -8,6 +8,7 @@
list_filter = ["feed_type", "is_defunct"],
ordering = ["title"],
search_fields = ["title", "public_url"],
raw_id_fields = ['owner'],
list_per_page = 500,
)

Expand Down
2 changes: 1 addition & 1 deletion django_website/aggregator/forms.py
Expand Up @@ -14,4 +14,4 @@ class FeedModelForm(forms.ModelForm):

class Meta:
model = Feed
exclude = ('is_defunct', 'feed_type')
exclude = ('is_defunct', 'feed_type', 'owner')
87 changes: 87 additions & 0 deletions django_website/aggregator/migrations/0003_add_feed_owner.py
@@ -0,0 +1,87 @@
# 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 'Feed.owner'
db.add_column('aggregator_feed', 'owner', self.gf('django.db.models.fields.related.ForeignKey')(blank=True, related_name='owned_feeds', null=True, to=orm['auth.User']), keep_default=False)


def backwards(self, orm):

# Deleting field 'Feed.owner'
db.delete_column('aggregator_feed', 'owner_id')


models = {
'aggregator.feed': {
'Meta': {'object_name': 'Feed'},
'feed_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['aggregator.FeedType']"}),
'feed_url': ('django.db.models.fields.URLField', [], {'unique': 'True', 'max_length': '500'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'is_defunct': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'owner': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'owned_feeds'", 'null': 'True', 'to': "orm['auth.User']"}),
'public_url': ('django.db.models.fields.URLField', [], {'max_length': '500'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '500'})
},
'aggregator.feeditem': {
'Meta': {'ordering': "('-date_modified',)", 'object_name': 'FeedItem'},
'date_modified': ('django.db.models.fields.DateTimeField', [], {}),
'feed': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['aggregator.Feed']"}),
'guid': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '500', 'db_index': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'link': ('django.db.models.fields.URLField', [], {'max_length': '500'}),
'summary': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '500'})
},
'aggregator.feedtype': {
'Meta': {'object_name': 'FeedType'},
'can_self_add': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '250'}),
'slug': ('django.db.models.fields.SlugField', [], {'max_length': '250', 'db_index': 'True'})
},
'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'})
}
}

complete_apps = ['aggregator']
2 changes: 2 additions & 0 deletions django_website/aggregator/models.py
@@ -1,4 +1,5 @@
from django.db import models
from django.contrib.auth.models import User

class FeedType(models.Model):
name = models.CharField(max_length=250)
Expand All @@ -17,6 +18,7 @@ class Feed(models.Model):
public_url = models.URLField(max_length=500)
is_defunct = models.BooleanField()
feed_type = models.ForeignKey(FeedType)
owner = models.ForeignKey(User, blank=True, null=True, related_name='owned_feeds')

def __unicode__(self):
return self.title
Expand Down
19 changes: 14 additions & 5 deletions django_website/aggregator/urls.py
Expand Up @@ -8,20 +8,29 @@
views.index,
name = 'community-index'
),
url(r'^mine/$',
views.my_feeds,
name = 'community-my-feeds'
),
url(
r'^(?P<feed_type_slug>[-\w]+)/$',
views.feed_list,
name = "community-feed-list"
),
url(
r'^add/$',
views.feed_type_list,
name = "community-add-feed-list"
),
url(
r'^add/(?P<feed_type_slug>[-\w]+)/$',
views.add_feed,
name = 'community-add-feed'
),
url(
r'^edit/(?P<feed_id>\d+)/$',
views.edit_feed,
name = 'community-edit-feed'
),
url(
r'^delete/(?P<feed_id>\d+)/$',
views.delete_feed,
name = 'community-delete-feed'
),
)

81 changes: 67 additions & 14 deletions django_website/aggregator/views.py
@@ -1,40 +1,93 @@
from __future__ import absolute_import

from django.shortcuts import render_to_response, get_object_or_404
from django.shortcuts import render_to_response, get_object_or_404, redirect
from django.template import RequestContext
from django.contrib.auth.decorators import login_required
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.views.generic.list_detail import object_list
from .models import FeedItem, Feed, FeedType
from .forms import FeedModelForm
from ..shortcuts import render

def index(request):
feedtype_list = FeedType.objects.all()
return render_to_response('aggregator/index.html',
{'feedtype_list': feedtype_list},
context_instance=RequestContext(request))
"""
Displays the latest feeds of each type.
"""
ctx = {'feedtype_list': FeedType.objects.all()}
return render(request, 'aggregator/index.html', ctx)

def feed_list(request, feed_type_slug):
"""
Shows the latest feeds for the given type.
"""
feed_type = get_object_or_404(FeedType, slug=feed_type_slug)
return object_list(request,
queryset = FeedItem.objects.filter(feed__feed_type=feed_type),
paginate_by = 10,
paginate_by = 25,
extra_context = {'feed_type': feed_type},
)

@login_required
def my_feeds(request):
"""
Lets the user see, edit, and delete all of their owned feeds.
"""
feed_types = FeedType.objects.all()
if not request.user.is_superuser:
feed_types = feed_types.filter(can_self_add=True)

ctx = {
'feeds': Feed.objects.filter(owner=request.user),
'feed_types': feed_types
}
return render(request, 'aggregator/my-feeds.html', ctx)

@login_required
def add_feed(request, feed_type_slug):
"""
Lets users add new feeds to the aggregator.
Users only get to add new feeds of types indicated as "can self add."
"""
ft = get_object_or_404(FeedType, slug=feed_type_slug, can_self_add=True)
if not ft.can_self_add and not request.user.is_superuser:
return render_to_response('aggregator/denied.html',
context_instance=RequestContext(request))
return render(request, 'aggregator/denied.html')

instance = Feed(feed_type=ft)
instance = Feed(feed_type=ft, owner=request.user)
f = FeedModelForm(request.POST or None, instance=instance)
if f.is_valid():
if f.save():
return HttpResponseRedirect(reverse('community-index'))
return render_to_response('aggregator/add_feed.html',
{'form':f, 'feed_type': ft},
context_instance=RequestContext(request))
f.save()
return redirect('community-index')

ctx = {'form': f, 'feed_type': ft, 'adding': True}
return render(request, 'aggregator/edit-feed.html', ctx)

@login_required
def edit_feed(request, feed_id):
"""
Lets a user edit a feed they've previously added.
Only feeds the user "owns" can be edited.
"""
feed = get_object_or_404(Feed, pk=feed_id, owner=request.user)
f = FeedModelForm(request.POST or None, instance=feed)
if f.is_valid():
f.save()
return redirect('community-my-feeds')

ctx = {'form': f, 'feed': feed, 'adding': False}
return render(request, 'aggregator/edit-feed.html', ctx)

@login_required
def delete_feed(request, feed_id):
"""
Lets a user delete a feed they've previously added.
Only feeds the user "owns" can be deleted.
"""
feed = get_object_or_404(Feed, pk=feed_id, owner=request.user)
if request.method == 'POST':
feed.delete()
return redirect('community-my-feeds')
return render(request, 'aggregator/delete-confirm.html', {'feed': feed})
34 changes: 34 additions & 0 deletions django_website/shortcuts.py
@@ -0,0 +1,34 @@
"""
Every site has one. This is mine.
"""

from django.http import HttpResponse
from django.template import loader, RequestContext

#
# Backported render() from Django 1.3.
#
def render(request, *args, **kwargs):
"""
Returns a HttpResponse whose content is filled with the result of calling
django.template.loader.render_to_string() with the passed arguments.
Uses a RequestContext by default.
"""
httpresponse_kwargs = {
'content_type': kwargs.pop('content_type', None),
'status': kwargs.pop('status', None),
}

if 'context_instance' in kwargs:
context_instance = kwargs.pop('context_instance')
if kwargs.get('current_app', None):
raise ValueError('If you provide a context_instance you must '
'set its current_app before calling render()')
else:
current_app = kwargs.pop('current_app', None)
context_instance = RequestContext(request, current_app=current_app)

kwargs['context_instance'] = context_instance

return HttpResponse(loader.render_to_string(*args, **kwargs),
**httpresponse_kwargs)
16 changes: 16 additions & 0 deletions django_website/templates/aggregator/delete-confirm.html
@@ -0,0 +1,16 @@
{% extends "base_community.html" %}

{% block content %}
<h1>Community</h1>

<h2 class="deck">Really delete {{ feed }}?</h2>

<p>
All items in the aggregator will be deleted, too. Because Jacob's
lazy, there's no "undo"!
</p>

<form class="wide" action="." method="post">
<p class="submit"><input type="submit" value="Yes, delete the feed."></p>
</form>
{% endblock %}
Expand Up @@ -3,7 +3,11 @@
{% block content %}
<h1>Community</h1>

<h2 class="deck">Add a {{ feed_type }} feed:</h2>
{% if adding %}
<h2 class="deck">Add a {{ feed_type }} feed:</h2>
{% else %}
<h2 class="deck">Edit {{ feed }}:</h2>
{% endif %}

<form method="POST" action="" id="add_feed_form" class="wide">
{% for field in form %}
Expand All @@ -15,7 +19,11 @@ <h2 class="deck">Add a {{ feed_type }} feed:</h2>
{{ field }}
</p>
{% endfor %}
<p class="submit"><input type="submit" value="Add Feed"></p>
{% if adding %}
<p class="submit"><input type="submit" value="Add Feed"></p>
{% else %}
<p class="submit"><input type="submit" value="Save"></p>
{% endif %}
</form>

{% endblock %}
24 changes: 24 additions & 0 deletions django_website/templates/aggregator/my-feeds.html
@@ -0,0 +1,24 @@
{% extends "base_community.html" %}

{% block content %}
<h1>Community</h1>

<h2 class="deck">Manage your community aggregator feeds:</h2>

<ul>
{% for feed in feeds %}
<li>
{{ feed }} (<code>{{ feed.feed_url }}</code>) —
<a href="{% url community-edit-feed feed.id %}">edit</a> |
<a href="{% url community-delete-feed feed.id %}">delete</a>
</li>
{% endfor %}
<li>Add a new feed:
{% for t in feed_types %}
<a href="{% url community-add-feed t.slug %}">{{ t }}</a>
{% if not forloop.last %}|{% endif %}
{% endfor %}
</li>
{# <li>Claim a feed already in the system.</li> #}
</ul>
{% endblock %}
16 changes: 5 additions & 11 deletions django_website/urls/www.py
Expand Up @@ -25,17 +25,11 @@

urlpatterns = patterns('',
url(r'^$', 'django.views.generic.simple.direct_to_template', {'template': 'homepage.html'}, name="homepage"),
(r'^accounts/', include('django_website.accounts.urls')),
(r'^admin/(.*)', admin.site.root),
(r'^comments/$', 'django.views.generic.list_detail.object_list', comments_info_dict),
(r'^comments/', include('django.contrib.comments.urls')),
url(r'^community/add/(?P<feed_type_slug>[-\w]+)/',
'django_website.aggregator.views.add_feed',
name='community-add-feed'),
url(r'^community/(?P<feed_type_slug>[-\w]+)/',
'django_website.aggregator.views.feed_list',
name="community-feed-list"),
url(r'^community/', 'django_website.aggregator.views.index', name='community-index'),
url(r'^accounts/', include('django_website.accounts.urls')),
url(r'^admin/', include(admin.site.urls)),
url(r'^comments/$', 'django.views.generic.list_detail.object_list', comments_info_dict),
url(r'^comments/', include('django.contrib.comments.urls')),
url(r'^community/', include('django_website.aggregator.urls')),
url(r'^contact/', include('django_website.contact.urls')),
url(r'^r/', include('django.conf.urls.shortcut')),

Expand Down

0 comments on commit ce959eb

Please sign in to comment.