Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Added the concept of "owned" feeds, and let users edit/delete their o…

…wn feeds.
  • Loading branch information...
commit ce959eb267f5f2b20bb58ce0d1365e62f1ba9893 1 parent b1db2b4
@jacobian jacobian authored
View
1  TODO
@@ -1,2 +1 @@
-* allow users to "own" feeds and self-edit
* allow users to "claim" legacy feeds (might do this by hand.)
View
1  django_website/aggregator/admin.py
@@ -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,
)
View
2  django_website/aggregator/forms.py
@@ -14,4 +14,4 @@ class FeedModelForm(forms.ModelForm):
class Meta:
model = Feed
- exclude = ('is_defunct', 'feed_type')
+ exclude = ('is_defunct', 'feed_type', 'owner')
View
87 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']
View
2  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)
@@ -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
View
19 django_website/aggregator/urls.py
@@ -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'
+ ),
)
View
81 django_website/aggregator/views.py
@@ -1,6 +1,6 @@
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
@@ -8,33 +8,86 @@
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})
View
34 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)
View
16 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 %}
View
12 ...ebsite/templates/aggregator/add_feed.html → ...bsite/templates/aggregator/edit-feed.html
@@ -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 %}
@@ -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 %}
View
24 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 %}
View
16 django_website/urls/www.py
@@ -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')),
Please sign in to comment.
Something went wrong with that request. Please try again.