both as a subscriber directly and as a planet administrator.
More to be done, not in the least in the design department, but getting
it in there so I don't delete it - and so that others can work on the
all-important design stuff!
--- /dev/null
+from django.contrib.auth.models import User
+import psycopg2
+
+class AuthBackend:
+ def authenticate(self, username=None, password=None):
+ conn = psycopg2.connect('host=wwwmaster.postgresql.org dbname=186_www user=auth_svc password=g7y3m9u8 sslmode=require')
+ try:
+ conn.set_client_encoding('UNICODE')
+ cur = conn.cursor()
+ cur.execute('SELECT * FROM community_login(%s,%s)', (username, password))
+ row = cur.fetchall()[0]
+ finally:
+ conn.close()
+
+ if row[1] == 1:
+ try:
+ user = User.objects.get(username=username)
+ except User.DoesNotExist:
+ # User doesn't exist yet
+ user = User(username=username, password='setmanually', email=row[3], first_name=row[2])
+ user.save()
+ return user
+ return None
+
+ def get_user(self, user_id):
+ try:
+ return User.objects.get(pk=user_id)
+ except User.DoesNotExist:
+ return None
--- /dev/null
+# Add any settings here to override the default ones
+
+DATABASE_NAME = 'planetbeta'
+DATABASE_USER = 'planetadmin'
+
+DEBUG = True
--- /dev/null
+#!/usr/bin/env python
+from django.core.management import execute_manager
+try:
+ import settings # Assumed to be in the same directory.
+except ImportError:
+ import sys
+ sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
+ sys.exit(1)
+
+if __name__ == "__main__":
+ execute_manager(settings)
--- /dev/null
+from django.db import models
+
+class Blog(models.Model):
+ feedurl = models.CharField(max_length=255, blank=False)
+ name = models.CharField(max_length=255, blank=False)
+ blogurl = models.CharField(max_length=255, blank=False)
+ lastget = models.DateTimeField(default='2000-01-01')
+ userid = models.CharField(max_length=255, blank=False)
+ approved = models.BooleanField()
+
+ def __str__(self):
+ return self.feedurl
+
+ @property
+ def approved_(self):
+ if self.approved:
+ return "Yes"
+ return "No"
+
+ class Meta:
+ db_table = 'planet\".\"feeds'
+ ordering = ['approved','name']
+
+ class Admin:
+ pass
+
+class Post(models.Model):
+ feed = models.ForeignKey(Blog,db_column='feed')
+ guid = models.CharField(max_length=255)
+ link = models.CharField(max_length=255)
+ txt = models.CharField(max_length=255)
+ dat = models.DateTimeField()
+ title = models.CharField(max_length=255)
+ guidisperma = models.BooleanField()
+ hidden = models.BooleanField()
+
+ def __str__(self):
+ return self.title
+
+ def visible(self):
+ if self.hidden:
+ return "Hidden"
+ return "Visible"
+
+ class Meta:
+ db_table = 'planet\".\"posts'
+ ordering = ['-dat']
+
+ class Admin:
+ pass
--- /dev/null
+{% extends "regbase.html" %}
+{%block regcontent %}
+<p>
+This is a list of the blogposts we have found in your feed.
+Please note that if you delete a post, it will be automatically
+re-added if it's still in your RSS. This can be used to force
+planet to fetch a new version of the post. If you want the post
+not to show up, it should stay in planet and be hidden.
+</p>
+<p>
+Return to <a href="../..">blog list</a>.
+</p>
+<table border="1" cellspacing="0" cellpadding="1">
+<tr>
+ <th>Date</th>
+ <th>Title</th>
+ <th>Status</th>
+ <th>Operation</th>
+</tr>
+{% for post in posts %}
+<tr valign="top">
+ <td>{{post.dat}}</td>
+ <td>{{post.title}}</td>
+ <td>{{post.visible}}</td>
+ <td>
+ {%if post.hidden%}<a href="unhide/{{post.id}}/">Unhide</a>{%else%}<a href="hide/{{post.id}}/">Hide</a>{%endif%}<br/>
+ <a href="delete/{{post.id}}/">Delete</a>
+ </td>
+</tr>
+{%endfor%}
+</table>
+{% endblock %}
--- /dev/null
+{% extends "regbase.html" %}
+{%block regcontent%}
+{%if blogs %}
+<p>
+We have the following blog(s) registered:
+</p>
+<table border="1" cellspacing="0" cellpadding="1">
+<tr>
+ <th>Userid</th>
+ <th>Name</th>
+ <th>Approved</th>
+ <th>Feed URL/Blog URL</th>
+ <th>Operations</th>
+{% if user.is_superuser %}
+ <th>Admin</th>
+{%endif%}
+</tr>
+{%for blog in blogs%}
+<tr valign="top" {%if user.is_superuser and not blog.approved%}bgcolor="red"{%endif%}>
+ <td>{{blog.userid}}</td>
+ <td>{%if user.is_superuser %}
+<form method="post" action="modify/{{blog.id}}/">
+<input type="text" name="blogname" value="{{blog.name}}">
+<input type="submit" value="Save">
+</form>
+{%else%}
+{{blog.name}}
+{%endif%}
+ </td>
+ <td>{{blog.approved_}}</td>
+ <td>{{blog.feedurl}}<br/>{{blog.blogurl}}</td>
+ <td>{% if blog.approved or user.is_superuser%}
+ <a href="blogposts/{{blog.id}}/">Posts</a><br/>
+ <a href="delete/{{blog.id}}/">Delete</a><br/>
+{%else%}
+Not approved yet.
+{%endif%}</td>
+{%if user.is_superuser %}
+<td>
+{%if blog.approved %}<a href="unapprove/{{blog.id}}/">Unapprove</a>{%else%}<a href="approve/{{blog.id}}/">Approve</a>{%endif%}<br/>
+{%if blog.userid %}<a href="detach/{{blog.id}}/">Detach from user</a><br/>{%endif%}
+ <a href="discover/{{blog.id}}/">Discover metadata</a><br/>
+{%if blog.blogurl %}<a href="undiscover/{{blog.id}}/">Undiscover metadata</a><br/>{%endif%}
+</td>
+{%endif%}
+</tr>
+{%endfor%}
+</table>
+{%else%}
+<p>We have no blogs registered to your account. To register
+a new blog or associate an existing one, please enter the
+URL to your RSS feed (PostgreSQL category only!) below.
+Note that if you attach an existing blog, it will temporarily
+be removed from planet while a moderator verifies that the
+attachment is correct.
+</p>
+<form method="post" action="new/">
+<input type="text" name="feedurl"><br/>
+<input type="submit" value="New blog">
+</form>
+{%endif%}
+{%if user.is_superuser %}
+<p>As superuser, you can add a new blog. Note that normally the user requests the addition and you
+then update it! Leave the userid blank to let the user attach it later, but that's a really ugly
+way to do it :-P</p>
+<form method="post" action="new/">
+<input type="text" name="feedurl"><br/>
+<input type="text" name="userid"><br/>
+<input type="submit" value="New blog">
+</form>
+{%endif%}
+{%endblock%}
--- /dev/null
+{%extends "base.tmpl" %}
+{%block content%}
+{%if user.is_authenticated %}
+<div style="float:right;"><a href="/register/logout">Log out</a></div>
+{%endif%}
+<h1>Welcome to planet administration</h1>
+{%if user.is_superuser %}
+<h2>You are registered as an administrator. BE CAREFUL!</h2>
+{% endif %}
+{%block regcontent%}{%endblock%}
+{%endblock%}
--- /dev/null
+{% extends "regbase.html" %}
+
+{% block regcontent %}
+
+ {% if form.errors %}
+ <p class="error">Sorry, that's not a valid username or password</p>
+ {% endif %}
+
+ <p>
+You need to log in to access your settings. This is done using a
+PostgreSQL community account. If you do not have one, go to the main
+website and <a href="http://www.postgresql.org/community/signup">sign up</a>.
+ </p>
+ <form action='.' method='post'>
+ <label for="username">User name:</label>
+ <input type="text" name="username" value="" id="username">
+ <label for="password">Password:</label>
+ <input type="password" name="password" value="" id="password">
+
+ <input type="submit" value="login" />
+ <input type="hidden" name="next" value="{{ next|escape }}" />
+ <form action='.' method='post'>
+
+{% endblock %}
+
--- /dev/null
+from django.conf.urls.defaults import *
+from django.contrib.auth.views import login, logout, logout_then_login
+
+# Uncomment the next two lines to enable the admin:
+# from django.contrib import admin
+# admin.autodiscover()
+
+urlpatterns = patterns('',
+ (r'^$', 'planetadmin.register.views.root'),
+ (r'^new/$', 'planetadmin.register.views.new'),
+ (r'^approve/(\d+)/$', 'planetadmin.register.views.approve'),
+ (r'^unapprove/(\d+)/$', 'planetadmin.register.views.unapprove'),
+ (r'^discover/(\d+)/$', 'planetadmin.register.views.discover'),
+ (r'^undiscover/(\d+)/$', 'planetadmin.register.views.undiscover'),
+ (r'^detach/(\d+)/$', 'planetadmin.register.views.detach'),
+ (r'^delete/(\d+)/$', 'planetadmin.register.views.delete'),
+ (r'^modify/(\d+)/$', 'planetadmin.register.views.modify'),
+
+ (r'^blogposts/(\d+)/$', 'planetadmin.register.views.blogposts'),
+ (r'^blogposts/(\d+)/hide/(\d+)/$', 'planetadmin.register.views.blogpost_hide'),
+ (r'^blogposts/(\d+)/unhide/(\d+)/$', 'planetadmin.register.views.blogpost_unhide'),
+ (r'^blogposts/(\d+)/delete/(\d+)/$', 'planetadmin.register.views.blogpost_delete'),
+
+ (r'^login/$', login),
+ (r'^logout/$', logout_then_login, {'login_url':'/'}),
+)
--- /dev/null
+from django.http import HttpResponse, HttpResponseRedirect
+from django.template import RequestContext
+from django.shortcuts import render_to_response, get_object_or_404
+from django.contrib.auth.decorators import login_required, user_passes_test
+
+from planetadmin.register.models import *
+
+import socket
+import feedparser
+
+def issuperuser(user):
+ return user.is_authenticated() and user.is_superuser
+
+class pExcept(Exception):
+ pass
+
+@login_required
+def root(request):
+ if request.user.is_superuser:
+ blogs = Blog.objects.all()
+ else:
+ blogs = Blog.objects.filter(userid=request.user.username)
+ return render_to_response('index.html',{
+ 'blogs': blogs,
+ }, context_instance=RequestContext(request))
+
+@login_required
+def new(request):
+ if not request.method== 'POST':
+ raise Exception('must be POST')
+ feedurl = request.POST['feedurl']
+ if not len(feedurl) > 1:
+ raise Exception('must include blog url!')
+
+ # See if we can find the blog already
+ try:
+ blog = Blog.objects.get(feedurl=feedurl)
+ except:
+ blog = None
+
+ if blog:
+ if blog.userid:
+ return HttpResponse("Specified blog is already registered to account '%s'" % (blog.userid))
+ # Found a match, so we're going to register this blog
+ # For safety reasons, we're going to require approval before we do it as well :-P
+ blog.userid = request.user.username
+ blog.approved = False
+ blog.save()
+ return HttpResponse('The blog has been attached to your account. For security reasons, it has been disapproved until a moderator has approved this connection.')
+
+ if not feedurl.startswith('http://'):
+ return HttpResponse('Only http served blogs are accepted!')
+
+ # Attempting to register a new blog. First let's see that we can download it
+ socket.setdefaulttimeout(20)
+ try:
+ feed = feedparser.parse(feedurl)
+ status = feed.status
+ lnk = feed.feed.link
+ l = len(feed.entries)
+ if l < 1:
+ return HttpResponse('Blog feed contains no entries.')
+ except Exception, e:
+ print e
+ return HttpResponse('Failed to download blog feed')
+ if not status == 200:
+ return HttpResponse('Attempt to download blog feed returned status %s.' % (status))
+
+ blog = Blog()
+ blog.name = request.user.first_name
+ if request.user.is_superuser:
+ blog.userid = request.POST['userid']
+ else:
+ blog.userid= request.user.username
+ blog.feedurl = feedurl
+ blog.blogurl = lnk
+ blog.approved = False
+ blog.save()
+ return HttpResponseRedirect('..')
+
+@login_required
+def delete(request, id):
+ blog = get_object_or_404(Blog, id=id)
+ if not request.user.is_superuser:
+ if not blog.userid == request.user.username:
+ return HttpResponse("You can only delete your own feeds! Don't try to hack!")
+ blog.delete()
+ return HttpResponseRedirect('../..')
+
+@user_passes_test(issuperuser)
+def modify(request, id):
+ blog = get_object_or_404(Blog, id=id)
+ blog.name = request.POST['blogname']
+ blog.save()
+ return HttpResponseRedirect('../..')
+
+@user_passes_test(issuperuser)
+def approve(request, id):
+ blog = get_object_or_404(Blog, id=id)
+ blog.approved = True
+ blog.save()
+ return HttpResponseRedirect('../..')
+
+@user_passes_test(issuperuser)
+def unapprove(request, id):
+ blog = get_object_or_404(Blog, id=id)
+ blog.approved = False
+ blog.save()
+ return HttpResponseRedirect('../..')
+
+@user_passes_test(issuperuser)
+def discover(request, id):
+ blog = get_object_or_404(Blog, id=id)
+
+ # Attempt to run the discover
+ socket.setdefaulttimeout(20)
+ try:
+ feed = feedparser.parse(blog.feedurl)
+ if not blog.blogurl == feed.feed.link:
+ blog.blogurl = feed.feed.link
+ blog.save()
+ except Exception, e:
+ return HttpResponse('Failed to discover metadata: %s' % (e))
+
+ return HttpResponseRedirect('../..')
+
+@user_passes_test(issuperuser)
+def undiscover(request, id):
+ blog = get_object_or_404(Blog, id=id)
+ blog.blogurl = ''
+ blog.save()
+ return HttpResponseRedirect('../..')
+
+@user_passes_test(issuperuser)
+def detach(request, id):
+ blog = get_object_or_404(Blog, id=id)
+ blog.userid = None
+ blog.save()
+ return HttpResponseRedirect('../..')
+
+@login_required
+def blogposts(request, id):
+ blog = get_object_or_404(Blog, id=id)
+ if not blog.userid == request.user.username and not request.user.is_superuser:
+ return HttpResponse("You can't view/edit somebody elses blog!")
+
+ posts = Post.objects.filter(feed=blog)
+
+ return render_to_response('blogposts.html',{
+ 'posts': posts,
+ }, context_instance=RequestContext(request))
+
+def __getvalidblogpost(request, blogid, postid):
+ blog = get_object_or_404(Blog, id=blogid)
+ post = get_object_or_404(Post, id=postid)
+ if not blog.userid == request.user.username and not request.user.is_superuser:
+ raise pExcept("You can't view/edit somebody elses blog!")
+ if not post.feed.id == blog.id:
+ raise pExcept("Blog does not match post")
+ return post
+
+def __setposthide(request, blogid, postid, status):
+ try:
+ post = __getvalidblogpost(request, blogid, postid)
+ except pExcept, e:
+ return HttpResponse(e)
+ post.hidden = status
+ post.save()
+ return HttpResponseRedirect('../..')
+
+@login_required
+def blogpost_hide(request, blogid, postid):
+ return __setposthide(request, blogid, postid, True)
+
+@login_required
+def blogpost_unhide(request, blogid, postid):
+ return __setposthide(request, blogid, postid, False)
+
+@login_required
+def blogpost_delete(request, blogid, postid):
+ try:
+ post = __getvalidblogpost(request, blogid, postid)
+ except pExcept, e:
+ return HttpResponse(e)
+
+ post.delete()
+ return HttpResponseRedirect('../..')
--- /dev/null
+# Django settings for planetadmin project.
+
+DEBUG = False
+
+ADMINS = (
+ ('PostgreSQL Webmaster', 'webmaster@postgresql.org'),
+)
+
+MANAGERS = ADMINS
+
+DATABASE_ENGINE = 'postgresql_psycopg2' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+DATABASE_NAME = 'planetbeta' # Or path to database file if using sqlite3.
+DATABASE_USER = 'planetadmin' # Not used with sqlite3.
+DATABASE_PASSWORD = '' # Not used with sqlite3.
+DATABASE_HOST = '/tmp' # Set to empty string for localhost. Not used with sqlite3.
+DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
+
+TIME_ZONE = 'GMT'
+LANGUAGE_CODE = 'en-us'
+
+SITE_ID = 1
+
+USE_I18N = False
+
+MEDIA_ROOT = ''
+MEDIA_URL = ''
+ADMIN_MEDIA_PREFIX = '/media/'
+
+SECRET_KEY = '_q-piuw^kw^v1f%b6nrla+p%=&1bt#z%c$ujhioxe^!z%8q1l0'
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+ 'django.template.loaders.filesystem.load_template_source',
+ 'django.template.loaders.app_directories.load_template_source',
+)
+
+MIDDLEWARE_CLASSES = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+)
+
+ROOT_URLCONF = 'planetadmin.urls'
+
+TEMPLATE_DIRS = (
+ # Refer back to main planet templates
+ "../template",
+)
+
+INSTALLED_APPS = (
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'planetadmin.register',
+)
+
+AUTHENTICATION_BACKENDS = (
+ 'planetadmin.auth.AuthBackend',
+)
+
+LOGIN_URL = '/register/login'
+
+# If there is a local_settings.py, let it override our settings
+try:
+ from local_settings import *
+except:
+ pass
+
--- /dev/null
+from django.conf.urls.defaults import *
+
+# Uncomment the next two lines to enable the admin:
+# from django.contrib import admin
+# admin.autodiscover()
+
+urlpatterns = patterns('',
+ # Example:
+ (r'^register/', include('planetadmin.register.urls')),
+)
<meta http-equiv="Content-Type" content="text/xhtml; charset=utf-8" />
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="alternate" type="application/rss+xml" title="Planet PostgreSQL" href="http://planet.postgresql.org/rss20.xml" />
- <style type="text/css" media="screen" title="Normal Text">@import url("css/planet.css");</style>
+ <style type="text/css" media="screen" title="Normal Text">@import url("/css/planet.css");</style>
</head>
<body>
<div id="planetWrap">