Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added the concept of "owned" feeds, and let users edit/delete their o…
…wn feeds.
- Loading branch information
Showing
12 changed files
with
261 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1 @@ | ||
* allow users to "own" feeds and self-edit | ||
* allow users to "claim" legacy feeds (might do this by hand.) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
87 changes: 87 additions & 0 deletions
87
django_website/aggregator/migrations/0003_add_feed_owner.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 %} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters