diff --git a/setup.py b/setup.py index 6478ee2..3546c0a 100644 --- a/setup.py +++ b/setup.py @@ -36,5 +36,5 @@ package_dir = {'': 'src'}, include_package_data = True, install_requires = ['django-social-auth>=0.6.7', - 'mailmanclient'] + 'mailmanclient', ] ) diff --git a/src/postorius/auth/decorators.py b/src/postorius/auth/decorators.py index 82876f3..b5ff2d9 100644 --- a/src/postorius/auth/decorators.py +++ b/src/postorius/auth/decorators.py @@ -18,11 +18,29 @@ """Postorius view decorators.""" +from django.contrib.auth import logout, authenticate, login from django.core.exceptions import PermissionDenied from postorius.models import (Domain, List, Member, MailmanUser, MailmanApiError, Mailman404Error) +def basic_auth_login(fn): + def wrapper(*args, **kwargs): + request = args[0] + if request.user.is_authenticated(): + print 'already logged in' + if not request.user.is_authenticated(): + if request.META.has_key('HTTP_AUTHORIZATION'): + authmeth, auth = request.META['HTTP_AUTHORIZATION'].split(' ', 1) + if authmeth.lower() == 'basic': + auth = auth.strip().decode('base64') + username, password = auth.split(':', 1) + user = authenticate(username=username, password=password) + if user: + login(request, user) + return fn(request, **kwargs) + return wrapper + def list_owner_required(fn): """Check if the logged in user is the list owner of the given list. @@ -65,3 +83,27 @@ user.is_list_moderator = True return fn(*args, **kwargs) return wrapper + + +def superuser_or_403(fn): + """Make sure that the logged in user is a superuser or otherwise raise + PermissionDenied. + Assumes the request object to be the first arg.""" + def wrapper(*args, **kwargs): + user = args[0].user + if not user.is_superuser: + raise PermissionDenied + return fn(*args, **kwargs) + return wrapper + + +def loggedin_or_403(fn): + """Make sure that the logged in user is not anonymous or otherwise raise + PermissionDenied. + Assumes the request object to be the first arg.""" + def wrapper(*args, **kwargs): + user = args[0].user + if not user.is_authenticated(): + raise PermissionDenied + return fn(*args, **kwargs) + return wrapper diff --git a/src/postorius/doc/_build/doctrees/development.doctree b/src/postorius/doc/_build/doctrees/development.doctree index 1d6bc19..0913ac9 100644 --- a/src/postorius/doc/_build/doctrees/development.doctree +++ b/src/postorius/doc/_build/doctrees/development.doctree Binary files differ diff --git a/src/postorius/doc/_build/doctrees/environment.pickle b/src/postorius/doc/_build/doctrees/environment.pickle index f044a40..836b875 100644 --- a/src/postorius/doc/_build/doctrees/environment.pickle +++ b/src/postorius/doc/_build/doctrees/environment.pickle Binary files differ diff --git a/src/postorius/doc/_build/doctrees/news.doctree b/src/postorius/doc/_build/doctrees/news.doctree index a017b50..d550137 100644 --- a/src/postorius/doc/_build/doctrees/news.doctree +++ b/src/postorius/doc/_build/doctrees/news.doctree Binary files differ diff --git a/src/postorius/doc/_build/html/_sources/development.txt b/src/postorius/doc/_build/html/_sources/development.txt index 76b5eb1..14e1240 100644 --- a/src/postorius/doc/_build/html/_sources/development.txt +++ b/src/postorius/doc/_build/html/_sources/development.txt @@ -92,12 +92,40 @@ There are a number of decorators to protect views from unauthorized users. -- ``@user_passes_test(lambda u: u.is_superuser)`` -- ``@login_required`` -- ``@list_owner_required`` -- ``@list_moderator_required`` +- ``@user_passes_test(lambda u: u.is_superuser)`` (redirects to login form) +- ``@login_required`` (redirects to login form) +- ``@list_owner_required`` (returns 403) +- ``@list_moderator_required`` (returns 403) +- ``@superuser_or_403`` (returns 403) +- ``@loggedin_or_403`` (returns 403) +- ``@basic_auth_login`` -Check out views/views.py for examples! +Check out ``views/views.py`` or ``views/api.py`` for examples! + +The last one (basic_auth_login) checks the request header for HTTP Basic Auth +credentials and uses those to authenticate against Django's session-based +mechanism. It can be used in cases where a view is accessed from other clients +than the web browser. + +Please make sure to put it outside the other auth decorators. + +Good: + +:: + + @basic_auth_login + @list_owner_required + def my_view_func(request): + ... + +Won't work, because list_owner_required will not recognize the user: + +:: + + @list_owner_required + @basic_auth_login + def my_view_func(request): + ... Accessing the Mailman API diff --git a/src/postorius/doc/_build/html/_sources/news.txt b/src/postorius/doc/_build/html/_sources/news.txt index b7d2b47..32fddea 100644 --- a/src/postorius/doc/_build/html/_sources/news.txt +++ b/src/postorius/doc/_build/html/_sources/news.txt @@ -39,6 +39,10 @@ * added a mailmanclient shell to use as a `manage.py` command (`python manage.py mmclient`) * use "url from future" template tag in all templates. Contributed by Richard Wackerbarth. * added "new user" form. Contributed by George Chatzisofroniou. +* added user subscription page +* added decorator to allow login via http basic auth (to allow non-browser clients to use API views) +* added api view for list index +* several changes regarding style and navigation structure 1.0 alpha 1 -- "Space Farm" diff --git a/src/postorius/doc/_build/html/development.html b/src/postorius/doc/_build/html/development.html index 478112e..c1e8131 100644 --- a/src/postorius/doc/_build/html/development.html +++ b/src/postorius/doc/_build/html/development.html @@ -123,12 +123,34 @@

There are a number of decorators to protect views from unauthorized users.

-

Check out views/views.py for examples!

+

Check out views/views.py or views/api.py for examples!

+

The last one (basic_auth_login) checks the request header for HTTP Basic Auth +credentials and uses those to authenticate against Django’s session-based +mechanism. It can be used in cases where a view is accessed from other clients +than the web browser.

+

Please make sure to put it outside the other auth decorators.

+

Good:

+
@basic_auth_login
+@list_owner_required
+def my_view_func(request):
+    ...
+
+
+

Won’t work, because list_owner_required will not recognize the user:

+
@list_owner_required
+@basic_auth_login
+def my_view_func(request):
+    ...
+
+

Accessing the Mailman APIΒΆ

diff --git a/src/postorius/doc/_build/html/news.html b/src/postorius/doc/_build/html/news.html index ab07210..3f98d51 100644 --- a/src/postorius/doc/_build/html/news.html +++ b/src/postorius/doc/_build/html/news.html @@ -87,6 +87,10 @@
  • added a mailmanclient shell to use as a manage.py command (python manage.py mmclient)
  • use “url from future” template tag in all templates. Contributed by Richard Wackerbarth.
  • added “new user” form. Contributed by George Chatzisofroniou.
  • +
  • added user subscription page
  • +
  • added decorator to allow login via http basic auth (to allow non-browser clients to use API views)
  • +
  • added api view for list index
  • +
  • several changes regarding style and navigation structure
  • diff --git a/src/postorius/doc/_build/html/searchindex.js b/src/postorius/doc/_build/html/searchindex.js index e8956ad..58c80d3 100644 --- a/src/postorius/doc/_build/html/searchindex.js +++ b/src/postorius/doc/_build/html/searchindex.js @@ -1 +1 @@ -Search.setIndex({objects:{tests:{tests:[4,0,1,""]},"postorius.tests":{test_utils:[0,0,1,""]}},terms:{all:[0,4,2,3],code:[0,4,3],forget:4,prefil:4,test_list_set:0,four:[],ackownledg:[],runserv:2,dirnam:[],follow:[4,2],content:[1,4],decid:[],depend:[3,2],authoris:[],send:[],granci:3,under:3,tour_:[],sens:0,introduc:0,merchant:3,sourc:0,everi:[],string:4,without:[0,4,3],far:[],none:4,offlin:[],util:[0,4,3],context_processor:0,mechan:4,veri:0,exact:4,relev:0,mailmanwebgsoc2011:[],common:[],testobject:4,contenttyp:[],administr:4,level:[],did:4,button:4,list:[0,4,3],liza:4,"try":4,item:4,adjust:[],localhost:[0,4],httpredirectobject:4,quick:0,prepar:2,dir:[],pleas:4,modelbackend:[],impli:3,fieldset_form:0,httpresponseredirect:4,cfg:[],seper:[],request:4,past:[],second:2,pass:0,download:2,further:[],click:4,compat:[],index:[0,4],what:[],name_of_permiss:[],appear:[],richard:3,sum:[],abl:[4,2],current:[0,4],delet:[0,4],new_list1:4,postoriu:[0,1,2,3],franziska:[],"new":[0,1,3,4],net:[],method:[],manag:[0,4,2,3],redirect:4,abov:4,gener:[0,3],never:2,remeb:[],here:0,themself:[],ubuntu:[],path:2,along:3,modifi:[4,3],sinc:[0,2],valu:4,search:[],mailinglist:[],vertifi:[],anymor:[],errorlog:[],step:[],jame:4,doctest:4,pick:0,action:4,chang:[0,2],mailman_media:[],ourselv:0,mock:0,contactpag:[],via:[],appli:[],app:[4,3],sponser:[],prefer:2,releas:2,api:[0,3],sponsel:[],foord:0,instal:[1,2,3,4],myownunittest:0,middlewar:[],from:[0,4,2,3],describ:[0,4],would:0,commun:[],doubl:4,two:0,perm:[],next:[],websit:[],few:[],call:[0,3],typo:3,recommend:[],dict:0,type:4,web_host:4,create_mock_list:0,mailman_django:[],abspath:[],relat:[0,4],ital:[],site:[4,2],warn:[4,2],trail:[],berlio:[],stick:[],autodoc:[],particular:3,dooo:[],hold:0,unpack:[],easiest:4,customlog:[],account:[3,2],join:[],reviewd:[],alia:2,setup:[4,2,3],work:0,uniqu:[],dev:[3,2],itself:0,can:[0,4,2,3],purpos:3,def:[],control:[0,3],defer:3,sqlite:2,prompt:2,login_requir:[0,4],tar:[],process:3,sudo:2,accept:3,topic:[],tag:3,want:[4,2],fieldset:0,create_mock_memb:0,create_mock_domain:0,nearli:[],alwai:[0,2],cours:[],multipl:[],listmembersviewtest:0,anoth:4,magigmock:0,faulti:[],georg:[4,3],write:0,how:[0,2],list_nam:0,reject:3,instead:[0,4],config:[],nasti:[],css:[0,2],updat:4,product:2,recogn:0,farm:3,pypi:2,after:[4,2],"long":[],usabl:[],befor:[0,4],wrong:4,date:2,end:[],data:4,"short":0,third:2,postfix:[],bind:[],bootstrap:[],credenti:0,django:[0,4,2,3],alias:3,environ:2,adverrtis:4,jain:3,enter:4,fallback:[],automaticli:[],egg:[],order:[0,2],listnam:4,help:0,move:3,becaus:[0,4],has_perm:[],"__future__":[],mailman_vers:0,style:0,render:[],fit:3,fix:3,browserid:3,unicode_liter:[],better:[],restart:[],helper:3,media_root:[],mail:[0,4,3],hidden:[],main:[],might:[],guarente:[],split:[],them:[0,2],good:[],"return":0,thei:[0,4],python:[0,4,2,3],databs:[],handi:0,auth:0,unfortuneatli:[],"break":[],mention:4,front:4,resourc:4,now:[0,4,2,3],introduct:[],get_list:0,term:3,benst:[],somewher:2,name:[0,4],anyth:[],edit:[4,3],simpl:0,postorius_error:[],didn:0,authent:0,separ:[0,4,3],easili:[],senarclen:3,each:[0,4],debug:4,found:[4,2],went:4,mailman_test_bindir:[],mean:0,everyth:[0,4],domain:[0,4,3],fail:[4,3],replac:[],idea:[],procedur:[],realli:4,redistribut:3,meta:4,"static":[0,2],connect:[0,3],our:4,patch:[],todo:[],dependeci:[],out:[0,3],variabl:0,shown:4,space:3,miss:3,robert:2,develop:[0,1,2,3,4],publish:3,api_us:4,profil:3,daniel:3,rest_serv:[],got:[],correct:[0,2],mailmancli:3,earlier:[],insid:[],workflow:0,free:[4,3],standard:0,standalon:[],reason:[],base:0,mailmanweb:[],dictionari:0,lists_of_domain:[],put:0,org:[0,3,2],restbackend:[],"40mail":4,launch:[],dev_setup:[],could:0,latest:2,membership:[0,4],keep:2,filter:[],thing:4,place:[],isn:[],root_urlconf:[],requireti:[],summari:4,first:[4,2],softwar:3,rang:[],directli:[0,4],prevent:[],feel:4,onc:[],number:0,natti:[],restrict:4,mai:[],instruct:[4,2],alreadi:4,done:4,messag:[0,4,3],authentif:4,owner:[0,4,3],stabl:4,installed_app:[],open:[0,4],gpl:[],differ:[0,2],rrze:[],my_own_test:0,script:0,benedict:3,hardcopi:[],licens:3,system:0,least:4,downsid:0,url_host:0,too:0,licenc:[],fullfil:[],"final":4,store:[],shell:[0,4,3],option:4,real_nam:4,tool:0,copi:3,specifi:4,gsoc:[],"var":[],part:4,"__test__":0,exactli:[],priveledg:[],serv:2,test_some_method:[],kind:0,provid:[0,4,2,3],remov:[4,3],structur:[0,3],new_domain:[],project:[0,3,2],reus:0,postorius_standalon:2,were:4,posit:4,minut:[],other:[0,4],fqdn_listnam:[0,4],pre:[],sai:[],runner:0,ani:[0,3],postorius_access:[],packag:2,have:[0,4,2,3],tabl:2,need:[0,4,2],element:[],wsgiscriptalia:2,engin:2,inform:[0,2],florian:[],destroi:[],self:[],note:[4,2],also:[0,2],ideal:0,exampl:[0,4],take:[],which:[0,4,2],combin:[],mock_memb:0,singl:[0,4],even:3,sure:[0,4,2],kati:4,allow:[4,2],shall:4,usernam:[],object:[0,4],asdasd:[],most:[0,4],plan:[],letter:4,watt:4,alpha:3,"class":0,icon:[],collectstat:2,don:[0,4],bzr:2,url:[0,4,3],doc:0,proud:[],cover:[0,2],temporili:4,doe:4,meanwhil:[],deni:2,pars:4,effect:[],usual:0,review:0,dot:0,wsgi:2,owner_address:0,show:[4,3],text:4,contact_address:0,session:4,permiss:4,corner:[],fine:[],find:0,magicmock:0,involv:0,absolut:[],onli:[0,4,2],eas:[],locat:2,launchpad:[0,2],copyright:3,explain:4,configur:[],apach:[3,2],behind:[],should:[0,4,2,3],theme:3,version:[0,3,2],suppos:[],templat:[0,3],folder:[0,2],local:[0,4,2],hope:3,media_url:[],hit:[],contribut:3,get:[0,4,2],familiar:0,"__file__":[],stop:4,obviou:0,absolute_import:[],csrf:3,report:0,subscript:4,requir:4,layout:0,retreiv:0,mail_host:[0,4],bar:[],template_dir:[],possibl:2,"public":3,reload:2,bad:[],integr:0,restadmin:[],mm_membership:4,where:0,view:[0,4,3],wiki:[],conform:3,set:[0,4,2,3],special:[],sometest:[],see:[4,2,3],domain_admin:[],result:0,respons:4,testcas:[],wonder:[],awar:4,statu:4,mailman3a7:[],correctli:[],databas:[0,4,2],someth:4,test_list_memb:0,state:0,quickest:2,between:4,"import":[0,4],awai:[],approach:0,email:4,realnam:[],unauthor:0,extens:[],spare:0,correclti:[],ipython:0,advertis:4,subfold:[],thank:3,both:[],protect:0,last:4,chain:0,plugin:[],admin:2,howev:[],etc:2,tour:0,instanc:[0,4],context:3,delete_list:4,logout:[],login:[4,3],com:4,load:4,english:4,simpli:4,point:0,instanti:[],overview:4,unittest:[],address:[0,4],virtualhost:2,mock_list:0,header:[],featur:0,non:[],shortcut:0,linux:4,guid:[0,4,2],assum:2,backend:[],quit:[],trunk:0,mailman:[0,1,2,3,4],coupl:4,"0a7":[],is_superus:0,three:0,been:4,compon:[],much:0,unsubscrib:[4,3],modif:[],addit:[],quickli:0,upcom:[],imag:[0,2],xxx:[],togeth:4,i18n:[],ngeorg:4,niederreit:2,those:4,"case":4,creativecommon:[],therefor:[],look:0,gnu:[0,3,2],plain:[],align:[],properti:0,easier:0,lesser:3,"while":[0,2],dashboard:4,publicli:2,error:3,exist:0,everyon:[],authentication_backend:[],anonymousus:0,default_nonmember_act:0,new_list:[],advantag:0,almost:2,demo:[],metric:3,list_own:4,worri:0,archiv:4,revis:[],"__init__":0,subscrib:[0,4,3],decor:[0,4],let:[0,4],welcom:[],author:0,receiv:3,suggest:0,make:[0,4,2],belong:4,same:4,member:[0,3],handl:[],html:0,gui:4,document:[0,3,2],mod_wsgi:[3,2],behav:0,acl:[],finish:4,http:[0,4,2,3],webserv:2,upon:[],mmclient:[0,3],moment:[],http_host:4,initi:3,mani:3,suit:[0,4],stack:[],expand:[],appropri:[],moder:[0,3],foundat:3,scenario:[],framework:[],api_pass:4,whole:0,well:[4,2],membership_set:[],person:0,client:[0,4,2],command:[0,3],thi:[0,4,2,3],choos:4,model:0,rout:0,left:[],summer:3,just:[0,2],rest:[0,4],indic:4,mailman_dev:0,mailman3:4,pep8:3,webui:[],test_util:0,yet:[],languag:4,web:[1,2,3],like:0,display_nam:0,easi:[],project_path:[],had:[],list_summari:4,littl:0,apache2:[],add:[0,4,3],valid:[0,4],lawrenc:[],discurag:0,save:[],modul:[0,4],build:0,bin:[],applic:[0,2],obsolet:3,stein:3,unter:[],read:[],big:0,testlist:0,test:[0,4,3],know:2,gsoc_mailman:[],press:4,cooki:[],bit:0,password:[],tweak:[],authbackend:[],apart:0,resid:[0,2],template_context_processor:[],success:[4,3],changelog:[1,3],restpass:[],server:[0,3,2],collect:2,href:[],setup_mm:4,either:4,chatzisofroni:3,page:[0,4],www:[0,3],right:[],acknowledg:[],creation:4,some:[0,4],back:[],propos:0,proper:3,create_list:[],funcit:[],librari:0,distribut:3,basic:[0,3],buildout:2,djangoproject:[],confirm:4,avoid:[],woun:[],token:3,list_moderator_requir:0,select:[],slash:[],necessari:2,foo:[],anna:3,refer:4,machin:4,core:[],who:[],run:[0,4,2],bold:[],usag:[],symlink:[],host:0,repositori:[],post:4,bazaar:[0,2],mm_new_domain:[],stage:[],src:0,about:0,central:[],usa:4,list_owner_requir:0,syncdb:2,mass_subscrib:4,rohan:3,side:[],permission_requir:[],srv:2,act:2,fals:4,discard:3,readi:2,processor:[],block:4,own:[0,4],addus:[],status_cod:4,pythonpath:2,xyz:[],within:4,someviewclass:[],easy_instal:2,warranti:3,creativ:[],empti:4,contrib:[],your:[0,2],merg:0,choosen:4,span:4,log:4,wai:[0,4,2],"40exampl":4,execut:[0,4],print:[0,4],submit:4,custom:0,avail:[0,4,2],start:[0,4,2],reli:[],interfac:[3,2],includ:4,create_mock_:0,superus:[0,2],systers_django:[],lambda:0,"function":[0,4],media:[],head:[],form:[0,4,3],offer:4,descrip:[],requestfactori:[],pwd:0,link:4,translat:4,teardown_mm:4,branch:[0,3,2],line:[4,2],"true":4,bug:0,info:[0,3],pull:2,succe:4,made:[0,4],render_mailman_them:[],bullet:[],"0b2":0,whether:4,access:[0,4,3],displai:4,below:4,memebership:[],otherwis:[],more:3,postmast:0,extend_ajax:[],later:[],creat:[0,4,2],hardcod:[],dure:[4,3],doesn:[],listsettingsviewtest:0,implement:3,file:[0,3,2],home:[],pip:2,wackerbarth:3,check:[0,4,3],probabl:0,again:0,coder:2,successfulli:[],googl:3,titl:[],user:[0,4,2,3],when:0,detail:[0,3,2],gettext:4,"default":0,mizyrycki:3,mock_list2:[],role:0,futur:3,rememb:[],writabl:2,you:[0,4,2,3],user_passes_test:0,servernam:[],nice:[0,4],michael:0,why:4,prequir:[],consid:[],stai:[],outdat:4,sphinx:0,faster:0,mock_domain:0,directori:[0,4,2],enjoi:4,bottom:[],descript:[0,4],rule:[],mailman_them:3,relvant:0,mass:[4,3],came:[],time:[4,2],escap:4,inc:3},objtypes:{"0":"py:module"},titles:["Development","Postorius - The New Mailman Web UI","Installation","News / Changelog","Using the Django App - Developers Resource (outdated)"],objnames:{"0":["py","module","Python module"]},filenames:["development","index","setup","news","using"]}) \ No newline at end of file +Search.setIndex({objects:{tests:{tests:[4,0,1,""]},"postorius.tests":{test_utils:[0,0,1,""]}},terms:{all:[0,4,2,3],code:[0,4,3],forget:4,prefil:4,test_list_set:0,four:[],ackownledg:[],runserv:2,dirnam:[],follow:[4,2],content:[1,4],decid:[],depend:[3,2],authoris:[],send:[],shall:4,granci:3,under:3,tour_:[],sens:0,introduc:0,merchant:3,sourc:0,everi:[],string:4,without:[0,4,3],far:[],none:4,offlin:[],util:[0,4,3],context_processor:0,mechan:[0,4],veri:0,exact:4,relev:0,mailmanwebgsoc2011:[],common:[],testobject:4,contenttyp:[],administr:4,level:[],did:4,button:4,list:[0,4,3],liza:4,"try":4,item:4,adjust:[],localhost:[0,4],quick:0,prepar:2,dir:[],pleas:[0,4],modelbackend:[],impli:3,fieldset_form:0,httpresponseredirect:4,cfg:[],seper:[],request:[0,4],past:[],second:2,pass:0,download:2,further:[],click:4,compat:[],index:[0,4,3],what:[],name_of_permiss:[],appear:[],richard:3,sum:[],abl:[4,2],current:[0,4],delet:[0,4],new_list1:4,postoriu:[0,1,2,3],franziska:[],"new":[0,1,3,4],net:[],method:[],manag:[0,4,2,3],redirect:[0,4],abov:4,gener:[0,3],never:2,remeb:[],here:0,themself:[],ubuntu:[],path:2,along:3,modifi:[4,3],sinc:[0,2],valu:4,search:[],mailinglist:[],vertifi:[],anymor:[],errorlog:[],step:[],jame:4,doctest:4,pick:0,action:4,chang:[0,3,2],mailman_media:[],ourselv:0,mock:0,contactpag:[],via:3,appli:[],app:[4,3],sponser:[],prefer:2,releas:2,api:[0,3],sponsel:[],foord:0,home:[],instal:[1,2,3,4],myownunittest:0,middlewar:[],from:[0,4,2,3],describ:[0,4],would:0,commun:[],doubl:4,two:0,perm:[],next:[],websit:[],few:[],call:[0,3],typo:3,recommend:[],dict:0,type:4,web_host:4,create_mock_list:0,mailman_django:[],abspath:[],relat:[0,4],ital:[],site:[4,2],warn:[4,2],trail:[],berlio:[],stick:[],autodoc:[],particular:3,dooo:[],hold:0,unpack:[],easiest:4,dot:0,customlog:[],account:[3,2],join:[],reviewd:[],alia:2,setup:[4,2,3],work:0,uniqu:[],dev:[3,2],itself:0,can:[0,4,2,3],purpos:3,def:0,control:[0,3],defer:3,sqlite:2,prompt:2,login_requir:[0,4],tar:[],process:3,sudo:2,accept:3,topic:[],tag:3,want:[4,2],fieldset:0,create_mock_memb:0,create_mock_domain:0,nearli:[],alwai:[0,2],cours:[],multipl:[],listmembersviewtest:0,anoth:4,magigmock:0,faulti:[],georg:[4,3],write:0,how:[0,2],list_nam:0,reject:3,instead:[0,4],config:[],nasti:[],css:[0,2],updat:4,product:2,recogn:0,farm:3,pypi:2,after:[4,2],"long":[],usabl:[],befor:[0,4],wrong:4,date:2,end:[],data:4,"short":0,third:2,postfix:[],bind:[],bootstrap:[],credenti:0,django:[0,4,2,3],alias:3,environ:2,adverrtis:4,jain:3,enter:4,fallback:[],automaticli:[],egg:[],order:[0,2],listnam:4,help:0,move:3,becaus:[0,4],has_perm:[],"__future__":[],mailman_vers:0,style:[0,3],render:[],fit:3,fix:3,browserid:3,unicode_liter:[],better:[],restart:[],helper:3,media_root:[],mail:[0,4,3],hidden:[],main:[],might:[],guarente:[],split:[],them:[0,2],good:0,"return":0,thei:[0,4],python:[0,4,2,3],databs:[],handi:0,auth:[0,3],unfortuneatli:[],"break":[],mention:4,front:4,resourc:4,now:[0,4,2,3],introduct:[],get_list:0,term:3,benst:[],somewher:2,name:[0,4],anyth:[],edit:[4,3],simpl:0,postorius_error:[],didn:0,authent:0,separ:[0,4,3],easili:[],senarclen:3,each:[0,4],debug:4,found:[4,2],went:4,mailman_test_bindir:[],mean:0,everyth:[0,4],domain:[0,4,3],fail:[4,3],replac:[],idea:[],procedur:[],realli:4,redistribut:3,meta:4,"static":[0,2],connect:[0,3],our:4,patch:[],todo:[],dependeci:[],out:[0,3],variabl:0,shown:4,won:0,space:3,miss:3,robert:2,develop:[0,1,2,3,4],publish:3,api_us:4,profil:3,daniel:3,rest_serv:[],got:[],correct:[0,2],mailmancli:3,earlier:[],insid:[],workflow:0,free:[4,3],standard:0,standalon:[],reason:[],base:0,mailmanweb:[],dictionari:0,lists_of_domain:[],put:0,org:[0,3,2],restbackend:[],"40mail":4,launch:[],could:0,latest:2,membership:[0,4],keep:2,filter:[],thing:4,place:[],isn:[],outsid:0,root_urlconf:[],requireti:[],summari:4,first:[4,2],softwar:3,rang:[],directli:[0,4],prevent:[],feel:4,onc:[],number:0,natti:[],restrict:4,mai:[],instruct:[4,2],alreadi:4,done:4,messag:[0,4,3],authentif:4,owner:[0,4,3],stabl:4,installed_app:[],open:[0,4],gpl:[],differ:[0,2],rrze:[],my_own_test:0,script:0,benedict:3,hardcopi:[],licens:3,system:0,least:4,downsid:0,url_host:0,too:0,licenc:[],fullfil:[],"final":4,store:[],shell:[0,4,3],option:4,real_nam:4,tool:0,copi:3,specifi:4,gsoc:[],"var":[],part:4,"__test__":0,exactli:[],than:0,priveledg:[],serv:2,test_some_method:[],kind:0,provid:[0,4,2,3],remov:[4,3],structur:[0,3],new_domain:[],project:[0,3,2],"__init__":0,reus:0,postorius_standalon:2,were:4,posit:4,minut:[],other:[0,4],fqdn_listnam:[0,4],browser:[0,3],pre:[],sai:[],runner:0,ani:[0,3],postorius_access:[],packag:2,have:[0,4,2,3],tabl:2,need:[0,4,2],element:[],wsgiscriptalia:2,engin:2,inform:[0,2],florian:[],destroi:[],self:[],note:[4,2],also:[0,2],ideal:0,exampl:[0,4],take:[],which:[0,4,2],combin:[],mock_memb:0,singl:[0,4],even:3,sure:[0,4,2],kati:4,allow:[4,2,3],httpredirectobject:4,usernam:[],object:[0,4],asdasd:[],most:[0,4],plan:[],letter:4,watt:4,alpha:3,"class":0,icon:[],collectstat:2,don:[0,4],bzr:2,url:[0,4,3],doc:0,later:[],cover:[0,2],temporili:4,doe:4,meanwhil:[],deni:2,pars:4,effect:[],usual:0,review:0,dev_setup:[],wsgi:2,owner_address:0,show:[4,3],text:4,contact_address:0,session:[0,4],permiss:4,corner:[],fine:[],find:0,magicmock:0,involv:0,absolut:[],onli:[0,4,2],eas:[],locat:2,launchpad:[0,2],copyright:3,explain:4,configur:[],apach:[3,2],behind:[],should:[0,4,2,3],theme:3,version:[0,3,2],suppos:[],central:[],templat:[0,3],folder:[0,2],local:[0,4,2],hope:3,media_url:[],hit:[],contribut:3,get:[0,4,2],familiar:0,"__file__":[],stop:4,obviou:0,absolute_import:[],csrf:3,report:0,subscript:[4,3],requir:4,layout:0,retreiv:0,mail_host:[0,4],bar:[],template_dir:[],possibl:2,"public":3,reload:2,bad:[],integr:0,restadmin:[],mm_membership:4,where:0,view:[0,4,3],wiki:[],conform:3,set:[0,4,2,3],special:[],sometest:[],see:[4,2,3],domain_admin:[],result:0,respons:4,testcas:[],wonder:[],awar:4,statu:4,mailman3a7:[],correctli:[],databas:[0,4,2],someth:4,test_list_memb:0,state:0,quickest:2,between:4,"import":[0,4],awai:[],approach:0,email:4,realnam:[],unauthor:0,extens:[],spare:0,correclti:[],ipython:0,advertis:4,subfold:[],thank:3,both:[],protect:0,last:[0,4],chain:0,plugin:[],admin:2,howev:[],against:0,etc:2,tour:0,instanc:[0,4],context:3,delete_list:4,logout:[],login:[0,4,3],com:4,load:4,english:4,simpli:4,point:0,instanti:[],overview:4,unittest:[],address:[0,4],virtualhost:2,mock_list:0,header:0,featur:0,non:3,shortcut:0,linux:4,guid:[0,4,2],assum:2,backend:[],quit:[],trunk:0,mailman:[0,1,2,3,4],coupl:4,"0a7":[],is_superus:0,three:0,been:4,compon:[],much:0,unsubscrib:[4,3],modif:[],addit:[],quickli:0,upcom:[],imag:[0,2],xxx:[],superuser_or_403:0,togeth:4,i18n:[],ngeorg:4,niederreit:2,those:[0,4],"case":[0,4],creativecommon:[],therefor:[],look:0,gnu:[0,3,2],plain:[],align:[],properti:0,easier:0,lesser:3,"while":[0,2],dashboard:4,publicli:2,error:3,exist:0,everyon:[],authentication_backend:[],anonymousus:0,default_nonmember_act:0,new_list:[],advantag:0,almost:2,demo:[],metric:3,list_own:4,worri:0,archiv:4,revis:[],sever:3,subscrib:[0,4,3],decor:[0,4,3],let:[0,4],welcom:[],author:0,receiv:3,suggest:0,make:[0,4,2],belong:4,same:4,member:[0,3],handl:[],html:0,gui:4,document:[0,3,2],mod_wsgi:[3,2],behav:0,acl:[],finish:4,http:[0,4,2,3],webserv:2,upon:[],mmclient:[0,3],moment:[],http_host:4,initi:3,mani:3,stack:[],expand:[],appropri:[],moder:[0,3],foundat:3,scenario:[],framework:[],api_pass:4,whole:0,well:[4,2],membership_set:[],person:0,client:[0,4,2,3],command:[0,3],thi:[0,4,2,3],choos:4,model:0,loggedin_or_403:0,rout:0,left:[],summer:3,just:[0,2],rest:[0,4],indic:4,mailman_dev:0,mailman3:4,pep8:3,webui:[],test_util:0,yet:[],languag:4,web:[0,1,2,3],like:0,display_nam:0,easi:[],project_path:[],had:[],list_summari:4,littl:0,apache2:[],add:[0,4,3],valid:[0,4],lawrenc:[],discurag:0,save:[],modul:[0,4],build:0,bin:[],applic:[0,2],obsolet:3,stein:3,unter:[],read:[],big:0,regard:3,testlist:0,test:[0,4,3],know:2,gsoc_mailman:[],press:4,cooki:[],bit:0,password:[],tweak:[],authbackend:[],apart:0,resid:[0,2],template_context_processor:[],success:[4,3],changelog:[1,3],restpass:[],server:[0,3,2],collect:2,href:[],setup_mm:4,either:4,chatzisofroni:3,page:[0,4,3],www:[0,3],right:[],acknowledg:[],creation:4,some:[0,4],back:[],propos:0,proper:3,create_list:[],funcit:[],librari:0,distribut:3,basic:[0,3],buildout:2,djangoproject:[],confirm:4,avoid:[],woun:[],token:3,list_moderator_requir:0,select:[],slash:[],necessari:2,foo:[],anna:3,refer:4,machin:4,core:[],who:[],run:[0,4,2],bold:[],usag:[],symlink:[],host:0,repositori:[],basic_auth_login:0,post:4,bazaar:[0,2],mm_new_domain:[],stage:[],src:0,about:0,create_mock_:0,usa:4,list_owner_requir:0,mass_subscrib:4,rohan:3,side:[],permission_requir:[],srv:2,act:2,fals:4,discard:3,readi:2,processor:[],block:4,own:[0,4],addus:[],status_cod:4,pythonpath:2,xyz:[],within:4,someviewclass:[],easy_instal:2,warranti:3,creativ:[],empti:4,contrib:[],your:[0,2],merg:0,choosen:4,span:4,log:4,wai:[0,4,2],"40exampl":4,execut:[0,4],print:[0,4],submit:4,custom:0,avail:[0,4,2],start:[0,4,2],reli:[],interfac:[3,2],includ:4,suit:[0,4],superus:[0,2],systers_django:[],lambda:0,"function":[0,4],media:[],head:[],my_view_func:0,form:[0,4,3],offer:4,descrip:[],requestfactori:[],navig:3,pwd:0,link:4,translat:4,teardown_mm:4,branch:[0,3,2],line:[4,2],"true":4,bug:0,info:[0,3],pull:2,succe:4,made:[0,4],render_mailman_them:[],bullet:[],"0b2":0,whether:4,access:[0,4,3],displai:4,below:4,memebership:[],otherwis:[],more:3,postmast:0,extend_ajax:[],proud:[],creat:[0,4,2],hardcod:[],dure:[4,3],doesn:[],listsettingsviewtest:0,implement:3,file:[0,3,2],syncdb:2,pip:2,wackerbarth:3,check:[0,4,3],probabl:0,again:0,coder:2,successfulli:[],googl:3,titl:[],user:[0,4,2,3],when:0,detail:[0,3,2],gettext:4,"default":0,mizyrycki:3,mock_list2:[],role:0,futur:3,rememb:[],writabl:2,you:[0,4,2,3],user_passes_test:0,servernam:[],nice:[0,4],michael:0,why:4,prequir:[],consid:[],stai:[],outdat:4,sphinx:0,faster:0,mock_domain:0,directori:[0,4,2],enjoi:4,bottom:[],descript:[0,4],rule:[],mailman_them:3,relvant:0,mass:[4,3],came:[],time:[4,2],escap:4,inc:3},objtypes:{"0":"py:module"},titles:["Development","Postorius - The New Mailman Web UI","Installation","News / Changelog","Using the Django App - Developers Resource (outdated)"],objnames:{"0":["py","module","Python module"]},filenames:["development","index","setup","news","using"]}) \ No newline at end of file diff --git a/src/postorius/doc/development.rst b/src/postorius/doc/development.rst index 76b5eb1..14e1240 100644 --- a/src/postorius/doc/development.rst +++ b/src/postorius/doc/development.rst @@ -92,12 +92,40 @@ There are a number of decorators to protect views from unauthorized users. -- ``@user_passes_test(lambda u: u.is_superuser)`` -- ``@login_required`` -- ``@list_owner_required`` -- ``@list_moderator_required`` +- ``@user_passes_test(lambda u: u.is_superuser)`` (redirects to login form) +- ``@login_required`` (redirects to login form) +- ``@list_owner_required`` (returns 403) +- ``@list_moderator_required`` (returns 403) +- ``@superuser_or_403`` (returns 403) +- ``@loggedin_or_403`` (returns 403) +- ``@basic_auth_login`` -Check out views/views.py for examples! +Check out ``views/views.py`` or ``views/api.py`` for examples! + +The last one (basic_auth_login) checks the request header for HTTP Basic Auth +credentials and uses those to authenticate against Django's session-based +mechanism. It can be used in cases where a view is accessed from other clients +than the web browser. + +Please make sure to put it outside the other auth decorators. + +Good: + +:: + + @basic_auth_login + @list_owner_required + def my_view_func(request): + ... + +Won't work, because list_owner_required will not recognize the user: + +:: + + @list_owner_required + @basic_auth_login + def my_view_func(request): + ... Accessing the Mailman API diff --git a/src/postorius/doc/news.rst b/src/postorius/doc/news.rst index b7d2b47..32fddea 100644 --- a/src/postorius/doc/news.rst +++ b/src/postorius/doc/news.rst @@ -39,6 +39,10 @@ * added a mailmanclient shell to use as a `manage.py` command (`python manage.py mmclient`) * use "url from future" template tag in all templates. Contributed by Richard Wackerbarth. * added "new user" form. Contributed by George Chatzisofroniou. +* added user subscription page +* added decorator to allow login via http basic auth (to allow non-browser clients to use API views) +* added api view for list index +* several changes regarding style and navigation structure 1.0 alpha 1 -- "Space Farm" diff --git a/src/postorius/models.py b/src/postorius/models.py index 71fb61c..25e18af 100644 --- a/src/postorius/models.py +++ b/src/postorius/models.py @@ -20,7 +20,7 @@ from django.conf import settings from django.contrib.auth.models import User -from django.db.models.signals import pre_save +from django.db.models.signals import pre_delete, pre_save from django.db import models from django.dispatch import receiver from django.http import Http404 @@ -88,6 +88,7 @@ def create(self, **kwargs): try: method = getattr(self.client, 'create_' + self.resource_name) + print kwargs return method(**kwargs) except AttributeError, e: raise MailmanApiError(e) @@ -174,9 +175,3 @@ """Member model class. """ objects = MailmanRestManager('member', 'members') - - -@receiver(pre_save, sender=User) -def user_create_callback(sender, **kwargs): - # inst = kwargs['instance'] - pass diff --git a/src/postorius/static/postorius/css/style.css b/src/postorius/static/postorius/css/style.css index 9095450..6d5879e 100755 --- a/src/postorius/static/postorius/css/style.css +++ b/src/postorius/static/postorius/css/style.css @@ -110,8 +110,9 @@ .mm_nav { padding: 10px 0 30px 0; margin: 20px 0; - border: solid #d8d8d8; + border-style: solid; border-width: 1px 0; + border-color: #e8e8e8 #fff #d8d8d8 #fff; background: -webkit-linear-gradient(top, white 0%,#EFEFEF 100%); background: -moz-linear-gradient(top, white 0%,#EFEFEF 100%); background: -ms-linear-gradient(top, white 0%,#EFEFEF 100%); diff --git a/src/postorius/templates/postorius/base_ajax.html b/src/postorius/templates/postorius/base_ajax.html index 75ddf24..524a005 100644 --- a/src/postorius/templates/postorius/base_ajax.html +++ b/src/postorius/templates/postorius/base_ajax.html @@ -1,3 +1,2 @@ - {% load i18n %} {% block header%}{% endblock %} diff --git a/src/postorius/templates/postorius/menu/user_nav.html b/src/postorius/templates/postorius/menu/user_nav.html index b748810..9eef812 100644 --- a/src/postorius/templates/postorius/menu/user_nav.html +++ b/src/postorius/templates/postorius/menu/user_nav.html @@ -5,6 +5,7 @@
    diff --git a/src/postorius/templates/postorius/user_subscriptions.html b/src/postorius/templates/postorius/user_subscriptions.html new file mode 100644 index 0000000..eab40d6 --- /dev/null +++ b/src/postorius/templates/postorius/user_subscriptions.html @@ -0,0 +1,31 @@ +{% extends extend_template %} +{% load url from future %} +{% load i18n %} + +{% block main %} + {% include 'postorius/menu/user_nav.html' %} +

    List Subscriptions

    + +

    You are subscribed to the following mailing lists:

    + + + + + + + + + + + + {% for subscription in memberships %} + + + + + + + {% endfor %} + +
    {% trans 'List Name' %}{% trans 'Subscription Address' %}{% trans 'Role' %}{% trans 'Delivery Mode' %}
    {{ subscription.fqdn_listname }}{{ subscription.address }}{{ subscription.role }}{{ subscription.delivery_mode }}
    +{% endblock main %} diff --git a/src/postorius/templates/postorius/users/index.html b/src/postorius/templates/postorius/users/index.html index 60db212..4b772e7 100644 --- a/src/postorius/templates/postorius/users/index.html +++ b/src/postorius/templates/postorius/users/index.html @@ -7,7 +7,8 @@ {% trans 'Users' %} {% if user.is_superuser %} {% endif %} diff --git a/src/postorius/tests/test_auth_decorators.py b/src/postorius/tests/test_auth_decorators.py index 012faf0..2017819 100644 --- a/src/postorius/tests/test_auth_decorators.py +++ b/src/postorius/tests/test_auth_decorators.py @@ -17,11 +17,13 @@ from django.contrib.auth.models import AnonymousUser, User from django.core.exceptions import PermissionDenied +from django.test.client import RequestFactory from django.utils import unittest from mock import patch from postorius.auth.decorators import (list_owner_required, - list_moderator_required) + list_moderator_required, + basic_auth_login) from postorius.models import (Domain, List, Member, MailmanUser, MailmanApiError, Mailman404Error) from mailmanclient import Client diff --git a/src/postorius/urls.py b/src/postorius/urls.py index 5803d81..f3f268f 100644 --- a/src/postorius/urls.py +++ b/src/postorius/urls.py @@ -32,9 +32,8 @@ url(r'^accounts/logout/$', 'user_logout', name='user_logout'), url(r'^accounts/profile/$', 'user_profile', name='user_profile'), url(r'^accounts/todos/$', 'user_todos', name='user_todos'), - url(r'^accounts/membership/(?:(?P[^/]+)/)?$', - 'membership_settings', kwargs={"tab": "membership"}, - name='membership_settings'), + url(r'^accounts/subscriptions/$', UserSubscriptionsView.as_view(), + name='user_subscriptions'), url(r'^accounts/mailmansettings/$', 'user_mailmansettings', name='user_mailmansettings'), @@ -81,4 +80,5 @@ url(r'^users/new/$', 'user_new', name='user_new'), url(r'^users/(?P[^/]+)/$', UserSummaryView.as_view(), name='user_summary'), + url(r'^api/lists/$', 'api_list_index', name='api_list_index'), ) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/src/postorius/views/__init__.py b/src/postorius/views/__init__.py index 9b944cc..edd5024 100644 --- a/src/postorius/views/__init__.py +++ b/src/postorius/views/__init__.py @@ -17,3 +17,4 @@ # Postorius. If not, see . from postorius.views.views import * +from postorius.views.api import * diff --git a/src/postorius/views/api.py b/src/postorius/views/api.py new file mode 100644 index 0000000..6d954de --- /dev/null +++ b/src/postorius/views/api.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 1998-2012 by the Free Software Foundation, Inc. +# +# This file is part of Postorius. +# +# Postorius is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# Postorius is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# Postorius. If not, see . + + +import re +import sys +import json +import logging +import requests + + +from django.conf import settings +from django.contrib import messages +from django.contrib.auth import logout, authenticate, login +from django.contrib.auth.decorators import (login_required, + permission_required, + user_passes_test) +from django.contrib.auth.forms import AuthenticationForm +from django.contrib.auth.models import User +from django.core.urlresolvers import reverse +from django.http import HttpResponse, HttpResponseRedirect +from django.shortcuts import render_to_response, redirect +from django.template import Context, loader, RequestContext +from django.utils.decorators import method_decorator +from django.utils.translation import gettext as _ +from urllib2 import HTTPError + +from mailmanclient import Client +from postorius import utils +from postorius.models import (Domain, List, Member, MailmanUser, + MailmanApiError, Mailman404Error) +from postorius.forms import * +from postorius.auth.decorators import * +from postorius.views.generic import MailingListView, MailmanUserView + + +logger = logging.getLogger(__name__) + + +@basic_auth_login +@loggedin_or_403 +def api_list_index(request): + client = Client('%s/3.0' % settings.REST_SERVER, + settings.API_USER, settings.API_PASS) + res, content = client._connection.call('lists') + return HttpResponse(json.dumps(content['entries']), + content_type="application/json") diff --git a/src/postorius/views/generic.py b/src/postorius/views/generic.py index 835346e..c1fc639 100644 --- a/src/postorius/views/generic.py +++ b/src/postorius/views/generic.py @@ -19,7 +19,7 @@ from django.shortcuts import render_to_response, redirect from django.template import Context, loader, RequestContext -from django.views.generic import TemplateView +from django.views.generic import TemplateView, View from postorius.models import (Domain, List, Member, MailmanUser, MailmanApiError, Mailman404Error) @@ -61,20 +61,30 @@ return address def _get_user(self, user_id): - user_obj = MailmanUser.objects.get_or_404(address=user_id) + try: + user_obj = MailmanUser.objects.get(address=user_id) + except Mailman404Error: + user_obj = None # replace display_name with first address if display_name is not set - if user_obj.display_name == 'None' or user_obj.display_name is None: - user_obj.display_name = '' - user_obj.first_address = self._get_first_address(user_obj) + if user_obj is not None: + if user_obj.display_name == 'None' or user_obj.display_name is None: + user_obj.display_name = '' + user_obj.first_address = self._get_first_address(user_obj) return user_obj def dispatch(self, request, *args, **kwargs): # get the user object. + user_id = None if 'user_id' in kwargs: + user_id = kwargs['user_id'] + elif request.user.is_authenticated(): + user_id = request.user.email + if user_id is not None: try: - self.mm_user = self._get_user(kwargs['user_id']) + self.mm_user = self._get_user(user_id) except MailmanApiError: return utils.render_api_error(request) + # set the template if 'template' in kwargs: self.template = kwargs['template'] diff --git a/src/postorius/views/views.py b/src/postorius/views/views.py index dd0943f..a7f6166 100644 --- a/src/postorius/views/views.py +++ b/src/postorius/views/views.py @@ -19,7 +19,9 @@ import re import sys +import json import logging +import requests from django.conf import settings @@ -28,7 +30,8 @@ from django.contrib.auth.decorators import (login_required, permission_required, user_passes_test) -from django.contrib.auth.forms import AuthenticationForm +from django.contrib.auth.forms import (AuthenticationForm, PasswordResetForm, + SetPasswordForm, PasswordChangeForm) from django.contrib.auth.models import User from django.core.urlresolvers import reverse from django.http import HttpResponse, HttpResponseRedirect @@ -43,7 +46,7 @@ from postorius.models import (Domain, List, Member, MailmanUser, MailmanApiError, Mailman404Error) from postorius.forms import * -from postorius.auth.decorators import list_owner_required +from postorius.auth.decorators import * from postorius.views.generic import MailingListView, MailmanUserView @@ -710,6 +713,39 @@ context_instance=RequestContext(request)) +class UserSubscriptionsView(MailmanUserView): + """Shows a summary of a user. + """ + + def _get_list(self, list_id): + if getattr(self, 'lists', None) is None: + self.lists = {} + if self.lists.get(list_id) is None: + self.lists[list_id] = List.objects.get(fqdn_listname=list_id) + return self.lists[list_id] + + def _get_memberships(self): + client = Client('%s/3.0' % settings.REST_SERVER, + settings.API_USER, settings.API_PASS) + memberships = [] + for a in self.mm_user.addresses: + members = client._connection.call('members/find', + {'subscriber': a}) + for m in members[1]['entries']: + mlist = self._get_list(m['list_id']) + memberships.append(dict(fqdn_listname=mlist.fqdn_listname, + role=m['role'], + delivery_mode=m['delivery_mode'], + address=a)) + return memberships + + def get(self, request): + memberships = self._get_memberships() + return render_to_response('postorius/user_subscriptions.html', + {'memberships': memberships}, + context_instance=RequestContext(request)) + + @user_passes_test(lambda u: u.is_superuser) def user_index(request, template='postorius/users/index.html'): """Show a table of all users. @@ -771,7 +807,7 @@ context_instance=RequestContext(request)) -@login_required +@login_required() def user_profile(request, user_email=None): if not request.user.is_authenticated(): return redirect('user_login')