Pyadmin Developer Documentation
Description
Pyadmin is an ongoing project intended to replace features in the legacy Admin application used by research staff for data management. Instead of a complete rewrite of the legacy software, Pyadmin is designed to support modular rewrites. This allows IT staff to rewrite existing functionality in Admin as it breaks as well as experiment with new functionality requested by various departments in a timely matter.Design
Pyadmin is a Python Django application which communicates with the legacy Java Admin application via a Python keystore server and REST API. This allows users to navigate between the two separate applications by persisting session information from their initial login.Requirements
- Python v3.4.2
- PostgreSQL v9.3.5
Installation
- If your system version of Python is less than v3.4.2, install the project into a Python virtual environment (https://docs.python.org/3/library/venv.html)
- Get the code, via Git! (http://git.votesmart.org/gitweb.cgi?p=pyadmin.git)
- Install Pip requirements:
$ pip install pyadmin/requirements.txt
- Edit settings.py with your database credentials
- Test the Pyadmin server:
$ python manage.py runserver
- Test the keystore server:
$ python keystore_server.py
Development
New modules in Pyadmin should be developed as regular Django apps. The following is an example for how to build a new module in Pyadmin and make it visible both through Admin and Pyadmin:User Story: I want to build a module in Pyadmin for research staff to use for checking the HTTP status of external URLs in our database. The module needs to be accessible from both Admin and Pyadmin so that users are not inconvenienced by having to login to two different pieces of software.
Changes to make to Pyadmin --
1. Create a Django app in Pyadmin called 'urlchecker':
$ django-admin startapp urlchecker
2. Create a 'urls.py' file in directory 'urlchecker' if it doesn't exist:
$ touch urlchecker/urls.py
3. In file 'urls.py', add an entry for module 'urlchecker':
urlpatterns = patterns('urlchecker.views',
url(r'^urlchecker/$', 'urlchecker'),
)
url(r'^urlchecker/$', 'urlchecker'),
)
4. When writing the view in file 'views.py' for path '/urlchecker/', be sure
to use the 'login_required' decorator. This redirects user to the Pyadmin login
page if something goes wrong with user authentication:
from django.contrib.auth.decorators import login_required
@login_required
def urlchecker(request):
pass
@login_required
def urlchecker(request):
pass
5. In project root 'pyadmin/settings.py' add an entry for module 'urlchecker'
to 'INSTALLED_APPS' list.
6. In project root 'pyadmin/urls.py' add an entry for module 'urlchecker' urls:
urlpatterns = patterns('',
url('^', include('urlchecker.urls')),
)
url('^', include('urlchecker.urls')),
)
7. Business logic for redirecting requests from Admin through the keystore
server and to Pyadmin is contained in Pyadmin module 'redirect' so that
developers should never have to make changes to file 'keystore_server.py':
def redirect_urlchecker(request, username=None):
if username and request.user.is_authenticated():
return redirect('/urlchecker/')
if username and request.user.is_authenticated():
return redirect('/urlchecker/')
Add a redirect function in 'redirect/views.py' for module 'urlchecker' and
modify existing function 'redirection_engine' in file 'redirect/views.py' for
module 'urlchecker':
def redirection_engine(request, username=None):
...
elif action = 'urlchecker':
return redirect_urlchecker(request, username)
...
...
elif action = 'urlchecker':
return redirect_urlchecker(request, username)
...
Changes to make to Admin --
8. In project 'admin' create directory
'/WEB-INF/src/org/pvs/admin/actions/urlChecker' and in directory 'urlChecker'
create file 'UrlCheckerAction.java'.
9. Use the following Java example as a template of what should go into file
'UrlCheckerAction.java':
package org.pvs.admin.actions.urlChecker;
import javax.servlet.http.*;
import org.apache.struts.action.*;
import java.util.*;
import java.io.*;
import org.pvs.admin.common.BaseActionSupport;
import org.pvs.admin.common.BaseAction;
import org.pvs.admin.common.Util;
import javax.servlet.http.HttpSession;
import org.apache.struts2.ServletActionContext;
import javax.naming.directory.*;
import org.pvs.admin.common.ReadConfig;
public class UrlCheckerAction extends BaseActionSupport {
private String name;
private String sessionId;
private String redirectTo;
private String source;
private HttpSession session;
public HttpSession getSessionId() {
return session;
}
public String execute() throws Exception {
sessionId = ServletActionContext.getRequest().getSession().getId();
session = ServletActionContext.getRequest().getSession();
redirectTo = "urlchecker";
source = "struts";
Attributes configuration = ReadConfig.getAttributes();
String pyadminUrl = configuration.get("pyadmin.url")
.toString().split(": ")[1];
name = pyadminUrl + "/authorization-redirect?sessionId=" +
sessionId + "&redirectTo=" + redirectTo + "&source=" + source;
return "success";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
import javax.servlet.http.*;
import org.apache.struts.action.*;
import java.util.*;
import java.io.*;
import org.pvs.admin.common.BaseActionSupport;
import org.pvs.admin.common.BaseAction;
import org.pvs.admin.common.Util;
import javax.servlet.http.HttpSession;
import org.apache.struts2.ServletActionContext;
import javax.naming.directory.*;
import org.pvs.admin.common.ReadConfig;
public class UrlCheckerAction extends BaseActionSupport {
private String name;
private String sessionId;
private String redirectTo;
private String source;
private HttpSession session;
public HttpSession getSessionId() {
return session;
}
public String execute() throws Exception {
sessionId = ServletActionContext.getRequest().getSession().getId();
session = ServletActionContext.getRequest().getSession();
redirectTo = "urlchecker";
source = "struts";
Attributes configuration = ReadConfig.getAttributes();
String pyadminUrl = configuration.get("pyadmin.url")
.toString().split(": ")[1];
name = pyadminUrl + "/authorization-redirect?sessionId=" +
sessionId + "&redirectTo=" + redirectTo + "&source=" + source;
return "success";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
10. In file '/WEB-INF/spring-appctx-struts.xml' add an entry for
'UrlCheckerAction':
<bean name="urlCheckerRedirect"
class="org.pvs.admin.urlChecker.UrlCheckerAction"
scope="prototype"
parent="baseActionConfig">
</bean>
class="org.pvs.admin.urlChecker.UrlCheckerAction"
scope="prototype"
parent="baseActionConfig">
</bean>
11. In file '/WEB-INF/src/struts.xml' add an entry for 'UrlCheckerRedirect':
<action name="urlCheckerRedirect" class="urlCheckerRedirect" method="execute"
result name="success" type="redirect">
<param name="location">
${name}
</param>
</action>
result name="success" type="redirect">
<param name="location">
${name}
</param>
</action>
12. In file '/tiles/common/menu.jsp' add an HTML hyperlink entry that will be
visible in the 'admin' user interface:
<a href="urlCheckerRedirect.action">Url Checker</a>
And that's it! Once again, no changes need to be made to the keystore server.
In order to test everything out, be sure to run admin, pyadmin and the
keystore server together.
Production Deployment
PyAdmin is deployed in production on server chickostick. It sits behind the internal staff firewall, so only users accessing the software from an IP address behind the DMZ have access. Given the existing setup of chickostick before PyAdmin was deployed, there was a problem deploying the software as WSGI via Apache. The easiest workaround for this problem was to build a simple, custom Tornado server with Python to deploy PyAdmin with - the code for which lives in file tornado_server.py. This server inherits Django's core WSGI handler and allows us to deploy the software in a single-threaded, asynchronous interface that sits behind a lighttpd reverse proxy which routes incoming traffic from pyadmin.votesmart.org. The tornado server is daemonized using a devops tools called Supervisor. Supervisor's configuration file is located at /etc/supervisord.config and if the process ever fails, it can be restarted via the global Supervisor command: `supervisord -c /etc/supervisord.config`.CategoryITDoc