commit
05d735adf2
2871 changed files with 498846 additions and 0 deletions
@ -0,0 +1,437 @@
@@ -0,0 +1,437 @@
|
||||
Django was originally created in late 2003 at World Online, the Web division |
||||
of the Lawrence Journal-World newspaper in Lawrence, Kansas. |
||||
|
||||
The PRIMARY AUTHORS are (and/or have been): |
||||
|
||||
* Adrian Holovaty |
||||
* Simon Willison |
||||
* Jacob Kaplan-Moss |
||||
* Wilson Miner |
||||
* Malcolm Tredinnick |
||||
* Georg "Hugo" Bauer |
||||
* Luke Plant |
||||
* Russell Keith-Magee |
||||
* Robert Wittams |
||||
* Gary Wilson |
||||
* Brian Rosner |
||||
|
||||
More information on the main contributors to Django can be found in |
||||
docs/internals/committers.txt. |
||||
|
||||
And here is an inevitably incomplete list of MUCH-APPRECIATED CONTRIBUTORS -- |
||||
people who have submitted patches, reported bugs, added translations, helped |
||||
answer newbie questions, and generally made Django that much better: |
||||
|
||||
ajs <adi@sieker.info> |
||||
alang@bright-green.com |
||||
Marty Alchin <gulopine@gamemusic.org> |
||||
Ahmad Alhashemi <trans@ahmadh.com> |
||||
Daniel Alves Barbosa de Oliveira Vaz <danielvaz@gmail.com> |
||||
AgarFu <heaven@croasanaso.sytes.net> |
||||
Dagur Páll Ammendrup <dagurp@gmail.com> |
||||
Collin Anderson <cmawebsite@gmail.com> |
||||
Jeff Anderson <jefferya@programmerq.net> |
||||
Andreas |
||||
andy@jadedplanet.net |
||||
Fabrice Aneche <akh@nobugware.com> |
||||
ant9000@netwise.it |
||||
Florian Apolloner <florian@apolloner.eu> |
||||
arien <regexbot@gmail.com> |
||||
David Ascher <http://ascher.ca/> |
||||
atlithorn <atlithorn@gmail.com> |
||||
Jökull Sólberg Auðunsson <jokullsolberg@gmail.com> |
||||
Arthur <avandorp@gmail.com> |
||||
av0000@mail.ru |
||||
David Avsajanishvili <avsd05@gmail.com> |
||||
Mike Axiak <axiak@mit.edu> |
||||
Niran Babalola <niran@niran.org> |
||||
Morten Bagai <m@bagai.com> |
||||
Mikaël Barbero <mikael.barbero nospam at nospam free.fr> |
||||
Jiri Barton |
||||
Ned Batchelder <http://www.nedbatchelder.com/> |
||||
batiste@dosimple.ch |
||||
Batman |
||||
Shannon -jj Behrens <http://jjinux.blogspot.com/> |
||||
Esdras Beleza <linux@esdrasbeleza.com> |
||||
Chris Bennett <chrisrbennett@yahoo.com> |
||||
James Bennett |
||||
Julian Bez |
||||
Arvis Bickovskis <viestards.lists@gmail.com> |
||||
Paul Bissex <http://e-scribe.com/> |
||||
Simon Blanchard |
||||
David Blewett <david@dawninglight.net> |
||||
Matt Boersma <ogghead@gmail.com> |
||||
boobsd@gmail.com |
||||
Andrew Brehaut <http://brehaut.net/blog> |
||||
brut.alll@gmail.com |
||||
btoll@bestweb.net |
||||
Jonathan Buchanan <jonathan.buchanan@gmail.com> |
||||
Keith Bussell <kbussell@gmail.com> |
||||
Chris Cahoon <chris.cahoon@gmail.com> |
||||
Juan Manuel Caicedo <juan.manuel.caicedo@gmail.com> |
||||
Trevor Caira <trevor@caira.com> |
||||
Ricardo Javier Cárdenes Medina <ricardo.cardenes@gmail.com> |
||||
Jeremy Carbaugh <jcarbaugh@gmail.com> |
||||
Graham Carlyle <graham.carlyle@maplecroft.net> |
||||
Antonio Cavedoni <http://cavedoni.com/> |
||||
C8E |
||||
cedric@terramater.net |
||||
Chris Chamberlin <dja@cdc.msbx.net> |
||||
Amit Chakradeo <http://amit.chakradeo.net/> |
||||
ChaosKCW |
||||
Sengtha Chay <sengtha@e-khmer.com> |
||||
ivan.chelubeev@gmail.com |
||||
Bryan Chow <bryan at verdjn dot com> |
||||
Antonis Christofides <anthony@itia.ntua.gr> |
||||
Michal Chruszcz <troll@pld-linux.org> |
||||
Can Burak Çilingir <canburak@cs.bilgi.edu.tr> |
||||
Ian Clelland <clelland@gmail.com> |
||||
Russell Cloran <russell@rucus.net> |
||||
colin@owlfish.com |
||||
crankycoder@gmail.com |
||||
Paul Collier <paul@paul-collier.com> |
||||
Pete Crosier <pete.crosier@gmail.com> |
||||
Matt Croydon <http://www.postneo.com/> |
||||
Leah Culver <leah@pownce.com> |
||||
flavio.curella@gmail.com |
||||
Jure Cuhalev <gandalf@owca.info> |
||||
John D'Agostino <john.dagostino@gmail.com> |
||||
dackze+django@gmail.com |
||||
Mihai Damian <yang_damian@yahoo.com> |
||||
David Danier <goliath.mailinglist@gmx.de> |
||||
Dirk Datzert <dummy@habmalnefrage.de> |
||||
Jonathan Daugherty (cygnus) <http://www.cprogrammer.org/> |
||||
dave@thebarproject.com |
||||
david@kazserve.org |
||||
Jason Davies (Esaj) <http://www.jasondavies.com/> |
||||
Richard Davies <richard.davies@elastichosts.com> |
||||
Alex Dedul |
||||
deric@monowerks.com |
||||
Max Derkachev <mderk@yandex.ru> |
||||
Rajesh Dhawan <rajesh.dhawan@gmail.com> |
||||
Sander Dijkhuis <sander.dijkhuis@gmail.com> |
||||
Jordan Dimov <s3x3y1@gmail.com> |
||||
dne@mayonnaise.net |
||||
dready <wil@mojipage.com> |
||||
Maximillian Dornseif <md@hudora.de> |
||||
Jeremy Dunck <http://dunck.us/> |
||||
Andrew Durdin <adurdin@gmail.com> |
||||
dusk@woofle.net |
||||
Andy Dustman <farcepest@gmail.com> |
||||
Clint Ecker |
||||
Nick Efford <nick@efford.org> |
||||
eibaan@gmail.com |
||||
enlight |
||||
Enrico <rico.bl@gmail.com> |
||||
A. Murat Eren <meren@pardus.org.tr> |
||||
Ludvig Ericson <ludvig.ericson@gmail.com> |
||||
eriks@win.tue.nl |
||||
Dirk Eschler <dirk.eschler@gmx.net> |
||||
Marc Fargas <telenieko@telenieko.com> |
||||
Szilveszter Farkas <szilveszter.farkas@gmail.com> |
||||
favo@exoweb.net |
||||
fdr <drfarina@gmail.com> |
||||
Dmitri Fedortchenko <zeraien@gmail.com> |
||||
Jonathan Feignberg <jdf@pobox.com> |
||||
Liang Feng <hutuworm@gmail.com> |
||||
Bill Fenner <fenner@gmail.com> |
||||
Stefane Fermgier <sf@fermigier.com> |
||||
Afonso Fernández Nogueira <fonzzo.django@gmail.com> |
||||
J. Pablo Fernandez <pupeno@pupeno.com> |
||||
Maciej Fijalkowski |
||||
Matthew Flanagan <http://wadofstuff.blogspot.com> |
||||
Eric Floehr <eric@intellovations.com> |
||||
Eric Florenzano <floguy@gmail.com> |
||||
Vincent Foley <vfoleybourgon@yahoo.ca> |
||||
Rudolph Froger <rfroger@estrate.nl> |
||||
Jorge Gajon <gajon@gajon.org> |
||||
gandalf@owca.info |
||||
Marc Garcia <marc.garcia@accopensys.com> |
||||
Alex Gaynor <alex.gaynor@gmail.com> |
||||
Andy Gayton <andy-django@thecablelounge.com> |
||||
Baishampayan Ghose |
||||
Dimitris Glezos <dimitris@glezos.com> |
||||
glin@seznam.cz |
||||
martin.glueck@gmail.com |
||||
Artyom Gnilov <boobsd@gmail.com> |
||||
Ben Godfrey <http://aftnn.org> |
||||
GomoX <gomo@datafull.com> |
||||
Guilherme Mesquita Gondim <semente@taurinus.org> |
||||
Mario Gonzalez <gonzalemario@gmail.com> |
||||
pradeep.gowda@gmail.com |
||||
Collin Grady <collin@collingrady.com> |
||||
Simon Greenhill <dev@simon.net.nz> |
||||
Owen Griffiths |
||||
Espen Grindhaug <http://grindhaug.org/> |
||||
Thomas Güttler <hv@tbz-pariv.de> |
||||
Horst Gutmann <zerok@zerokspot.com> |
||||
dAniel hAhler |
||||
hambaloney |
||||
Brian Harring <ferringb@gmail.com> |
||||
Brant Harris |
||||
Hawkeye |
||||
Joe Heck <http://www.rhonabwy.com/wp/> |
||||
Joel Heenan <joelh-django@planetjoel.com> |
||||
Mikko Hellsing <mikko@sorl.net> |
||||
Sebastian Hillig <sebastian.hillig@gmail.com> |
||||
hipertracker@gmail.com |
||||
Deryck Hodge <http://www.devurandom.org/> |
||||
Brett Hoerner <bretthoerner@bretthoerner.com> |
||||
Eric Holscher <http://ericholscher.com> |
||||
Ian Holsman <http://feh.holsman.net/> |
||||
Kieran Holland <http://www.kieranholland.com> |
||||
Sung-Jin Hong <serialx.net@gmail.com> |
||||
Richard House <Richard.House@i-logue.com> |
||||
Robert Rock Howard <http://djangomojo.com/> |
||||
John Huddleston <huddlej@wwu.edu> |
||||
Rob Hudson <http://rob.cogit8.org/> |
||||
Jason Huggins <http://www.jrandolph.com/blog/> |
||||
Hyun Mi Ae |
||||
Ibon <ibonso@gmail.com> |
||||
Tom Insam |
||||
Baurzhan Ismagulov <ibr@radix50.net> |
||||
james_027@yahoo.com |
||||
jcrasta@gmail.com |
||||
jdetaeye |
||||
Zak Johnson <zakj@nox.cx> |
||||
Nis Jørgensen <nis@superlativ.dk> |
||||
Michael Josephson <http://www.sdjournal.com/> |
||||
jpellerin@gmail.com |
||||
Julia Elman |
||||
junzhang.jn@gmail.com |
||||
Grigory Fateyev <greg@dial.com.ru> |
||||
Antti Kaihola <http://akaihola.blogspot.com/> |
||||
Bahadır Kandemir <bahadir@pardus.org.tr> |
||||
Karderio <karderio@gmail.com> |
||||
Nagy Károly <charlie@rendszergazda.com> |
||||
Erik Karulf <erik@karulf.com> |
||||
Ben Dean Kawamura <ben.dean.kawamura@gmail.com> |
||||
Ian G. Kelly <ian.g.kelly@gmail.com> |
||||
Thomas Kerpe <thomas@kerpe.net> |
||||
Ossama M. Khayat <okhayat@yahoo.com> |
||||
Ben Khoo <khoobks@westnet.com.au> |
||||
Garth Kidd <http://www.deadlybloodyserious.com/> |
||||
kilian <kilian.cavalotti@lip6.fr> |
||||
Sune Kirkeby <http://ibofobi.dk/> |
||||
Bastian Kleineidam <calvin@debian.org> |
||||
Cameron Knight (ckknight) |
||||
Nena Kojadin <nena@kiberpipa.org> |
||||
Igor Kolar <ike@email.si> |
||||
Gasper Koren |
||||
Martin Kosír <martin@martinkosir.net> |
||||
Meir Kriheli <http://mksoft.co.il/> |
||||
Bruce Kroeze <http://coderseye.com/> |
||||
krzysiek.pawlik@silvermedia.pl |
||||
Joseph Kocherhans |
||||
konrad@gwu.edu |
||||
knox <christobzr@gmail.com> |
||||
David Krauth |
||||
kurtiss@meetro.com |
||||
Panos Laganakos <panos.laganakos@gmail.com> |
||||
lakin.wecker@gmail.com |
||||
Nick Lane <nick.lane.au@gmail.com> |
||||
Stuart Langridge <http://www.kryogenix.org/> |
||||
Paul Lanier <planier@google.com> |
||||
Nicola Larosa <nico@teknico.net> |
||||
Finn Gruwier Larsen <finn@gruwier.dk> |
||||
Lau Bech Lauritzen |
||||
Rune Rønde Laursen <runerl@skjoldhoej.dk> |
||||
Eugene Lazutkin <http://lazutkin.com/blog/> |
||||
lcordier@point45.com |
||||
Jeong-Min Lee <falsetru@gmail.com> |
||||
Jannis Leidel <jl@websushi.org> |
||||
Christopher Lenz <http://www.cmlenz.net/> |
||||
lerouxb@gmail.com |
||||
Piotr Lewandowski <piotr.lewandowski@gmail.com> |
||||
Waylan Limberg <waylan@gmail.com> |
||||
limodou |
||||
Philip Lindborg <philip.lindborg@gmail.com> |
||||
Simon Litchfield <simon@quo.com.au> |
||||
Daniel Lindsley <polarcowz@gmail.com> |
||||
Trey Long <trey@ktrl.com> |
||||
msaelices <msaelices@gmail.com> |
||||
Matt McClanahan <http://mmcc.cx/> |
||||
Frantisek Malina <vizualbod@vizualbod.com> |
||||
Martin Maney <http://www.chipy.org/Martin_Maney> |
||||
masonsimon+django@gmail.com |
||||
Manuzhai |
||||
Petr Marhoun <petr.marhoun@gmail.com> |
||||
Petar Marić <http://www.petarmaric.com/> |
||||
Nuno Mariz <nmariz@gmail.com> |
||||
Marijn Vriens <marijn@metronomo.cl> |
||||
mark@junklight.com |
||||
Orestis Markou <orestis@orestis.gr> |
||||
Takashi Matsuo <matsuo.takashi@gmail.com> |
||||
Yasushi Masuda <whosaysni@gmail.com> |
||||
mattycakes@gmail.com |
||||
Jason McBrayer <http://www.carcosa.net/jason/> |
||||
Kevin McConnell <kevin.mcconnell@gmail.com> |
||||
mccutchen@gmail.com |
||||
Christian Metts |
||||
michael.mcewan@gmail.com |
||||
michal@plovarna.cz |
||||
Slawek Mikula <slawek dot mikula at gmail dot com> |
||||
mitakummaa@gmail.com |
||||
mmarshall |
||||
Andreas Mock <andreas.mock@web.de> |
||||
Reza Mohammadi <reza@zeerak.ir> |
||||
Aljosa Mohorovic <aljosa.mohorovic@gmail.com> |
||||
Ramiro Morales <rm0@gmx.net> |
||||
Eric Moritz <http://eric.themoritzfamily.com/> |
||||
mrmachine <real.human@mrmachine.net> |
||||
Robin Munn <http://www.geekforgod.com/> |
||||
James Murty |
||||
msundstr |
||||
Robert Myers <myer0052@gmail.com> |
||||
Nebojša Dorđević |
||||
Doug Napoleone <doug@dougma.com> |
||||
Gopal Narayanan <gopastro@gmail.com> |
||||
Fraser Nevett <mail@nevett.org> |
||||
Sam Newman <http://www.magpiebrain.com/> |
||||
Afonso Fernández Nogueira <fonzzo.django@gmail.com> |
||||
Neal Norwitz <nnorwitz@google.com> |
||||
Todd O'Bryan <toddobryan@mac.com> |
||||
oggie rob <oz.robharvey@gmail.com> |
||||
oggy <ognjen.maric@gmail.com> |
||||
Jay Parlar <parlar@gmail.com> |
||||
Carlos Eduardo de Paula <carlosedp@gmail.com> |
||||
pavithran s <pavithran.s@gmail.com> |
||||
Barry Pederson <bp@barryp.org> |
||||
permonik@mesias.brnonet.cz |
||||
peter@mymart.com |
||||
pgross@thoughtworks.com |
||||
phaedo <http://phaedo.cx/> |
||||
Julien Phalip <http://www.julienphalip.com> |
||||
phil@produxion.net |
||||
phil.h.smith@gmail.com |
||||
Gustavo Picon |
||||
Michael Placentra II <someone@michaelplacentra2.net> |
||||
Luke Plant <http://lukeplant.me.uk/> |
||||
plisk |
||||
Mihai Preda <mihai_preda@yahoo.com> |
||||
Daniel Poelzleithner <http://poelzi.org/> |
||||
polpak@yahoo.com |
||||
Matthias Pronk <django@masida.nl> |
||||
Jyrki Pulliainen <jyrki.pulliainen@gmail.com> |
||||
Thejaswi Puthraya <thejaswi.puthraya@gmail.com> |
||||
Johann Queuniet <johann.queuniet@adh.naellia.eu> |
||||
Jan Rademaker |
||||
Michael Radziej <mir@noris.de> |
||||
Laurent Rahuel <laurent.rahuel@gmail.com> |
||||
Luciano Ramalho |
||||
Amit Ramon <amit.ramon@gmail.com> |
||||
Philippe Raoult <philippe.raoult@n2nsoft.com> |
||||
Massimiliano Ravelli <massimiliano.ravelli@gmail.com> |
||||
Brian Ray <http://brianray.chipy.org/> |
||||
remco@diji.biz |
||||
David Reynolds <david@reynoldsfamily.org.uk> |
||||
rhettg@gmail.com |
||||
ricardojbarrios@gmail.com |
||||
Mike Richardson |
||||
Matt Riggott |
||||
Henrique Romano <onaiort@gmail.com> |
||||
Armin Ronacher |
||||
Daniel Roseman <http://roseman.org.uk/> |
||||
Brian Rosner <brosner@gmail.com> |
||||
Rozza <ross.lawley@gmail.com> |
||||
Oliver Rutherfurd <http://rutherfurd.net/> |
||||
ryankanno |
||||
Manuel Saelices <msaelices@yaco.es> |
||||
Ivan Sagalaev (Maniac) <http://www.softwaremaniacs.org/> |
||||
Vinay Sajip <vinay_sajip@yahoo.co.uk> |
||||
David Schein |
||||
scott@staplefish.com |
||||
Ilya Semenov <semenov@inetss.com> |
||||
serbaut@gmail.com |
||||
John Shaffer <jshaffer2112@gmail.com> |
||||
Pete Shinners <pete@shinners.org> |
||||
Leo Shklovskii |
||||
jason.sidabras@gmail.com |
||||
Brenton Simpson <http://theillustratedlife.com> |
||||
Jozko Skrablin <jozko.skrablin@gmail.com> |
||||
Ben Slavin <benjamin.slavin@gmail.com> |
||||
sloonz <simon.lipp@insa-lyon.fr> |
||||
SmileyChris <smileychris@gmail.com> |
||||
Warren Smith <warren@wandrsmith.net> |
||||
smurf@smurf.noris.de |
||||
Vsevolod Solovyov |
||||
sopel |
||||
Leo Soto <leo.soto@gmail.com> |
||||
Wiliam Alves de Souza <wiliamsouza83@gmail.com> |
||||
Don Spaulding <donspauldingii@gmail.com> |
||||
Bjørn Stabell <bjorn@exoweb.net> |
||||
Georgi Stanojevski <glisha@gmail.com> |
||||
starrynight <cmorgh@gmail.com> |
||||
Vasiliy Stavenko <stavenko@gmail.com> |
||||
Thomas Steinacher <http://www.eggdrop.ch/> |
||||
Johan C. Stöver <johan@nilling.nl> |
||||
nowell strite |
||||
Thomas Stromberg <tstromberg@google.com> |
||||
Sundance |
||||
SuperJared |
||||
Radek Švarz <http://www.svarz.cz/translate/> |
||||
Swaroop C H <http://www.swaroopch.info> |
||||
Aaron Swartz <http://www.aaronsw.com/> |
||||
Ville Säävuori <http://www.unessa.net/> |
||||
Mart Sõmermaa <http://mrts.pri.ee/> |
||||
Christian Tanzer <tanzer@swing.co.at> |
||||
Tyler Tarabula <tyler.tarabula@gmail.com> |
||||
Tyson Tate <tyson@fallingbullets.com> |
||||
Frank Tegtmeyer <fte@fte.to> |
||||
Terry Huang <terryh.tp@gmail.com> |
||||
thebjorn <bp@datakortet.no> |
||||
Zach Thompson <zthompson47@gmail.com> |
||||
Michael Thornhill |
||||
Deepak Thukral <deep.thukral@gmail.com> |
||||
tibimicu@gmx.net |
||||
tobias@neuyork.de |
||||
Tom Tobin |
||||
Joe Topjian <http://joe.terrarum.net/geek/code/python/django/> |
||||
torne-django@wolfpuppy.org.uk |
||||
Karen Tracey <graybark@bellsouth.net> |
||||
Jeff Triplett <jeff.triplett@gmail.com> |
||||
tstromberg@google.com |
||||
Makoto Tsuyuki <mtsuyuki@gmail.com> |
||||
tt@gurgle.no |
||||
David Tulig <david.tulig@gmail.com> |
||||
Amit Upadhyay <http://www.amitu.com/blog/> |
||||
Geert Vanderkelen |
||||
I.S. van Oostveen <v.oostveen@idca.nl> |
||||
viestards.lists@gmail.com |
||||
George Vilches <gav@thataddress.com> |
||||
Vlado <vlado@labath.org> |
||||
Milton Waddams |
||||
Chris Wagner <cw264701@ohio.edu> |
||||
wam-djangobug@wamber.net |
||||
Wang Chun <wangchun@exoweb.net> |
||||
Filip Wasilewski <filip.wasilewski@gmail.com> |
||||
Dan Watson <http://theidioteque.net/> |
||||
Joel Watts <joel@joelwatts.com> |
||||
Chris Wesseling <Chris.Wesseling@cwi.nl> |
||||
James Wheare <django@sparemint.com> |
||||
Mike Wiacek <mjwiacek@google.com> |
||||
charly.wilhelm@gmail.com |
||||
Rachel Willmer <http://www.willmer.com/kb/> |
||||
Gary Wilson <gary.wilson@gmail.com> |
||||
Jakub Wilk <ubanus@users.sf.net> |
||||
Jakub Wiśniowski <restless.being@gmail.com> |
||||
Maciej Wiśniowski <pigletto@gmail.com> |
||||
wojtek |
||||
Jason Yan <tailofthesun@gmail.com> |
||||
ye7cakf02@sneakemail.com |
||||
ymasuda@ethercube.com |
||||
Jarek Zgoda <jarek.zgoda@gmail.com> |
||||
Cheng Zhang |
||||
|
||||
A big THANK YOU goes to: |
||||
|
||||
Rob Curley and Ralph Gage for letting us open-source Django. |
||||
|
||||
Frank Wiles for making excellent arguments for open-sourcing, and for |
||||
his sage sysadmin advice. |
||||
|
||||
Ian Bicking for convincing Adrian to ditch code generation. |
||||
|
||||
Mark Pilgrim for diveintopython.org. |
||||
|
||||
Guido van Rossum for creating Python. |
||||
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
Thanks for downloading Django. |
||||
|
||||
To install it, make sure you have Python 2.3 or greater installed. Then run |
||||
this command from the command prompt: |
||||
|
||||
python setup.py install |
||||
|
||||
Note this requires a working Internet connection if you don't already have the |
||||
Python utility "setuptools" installed. |
||||
|
||||
AS AN ALTERNATIVE, you can just copy the entire "django" directory to Python's |
||||
site-packages directory, which is located wherever your Python installation |
||||
lives. Some places you might check are: |
||||
|
||||
/usr/lib/python2.4/site-packages (Unix, Python 2.4) |
||||
/usr/lib/python2.3/site-packages (Unix, Python 2.3) |
||||
C:\\PYTHON\site-packages (Windows) |
||||
|
||||
This second solution does not require a working Internet connection; it |
||||
bypasses "setuptools" entirely. |
||||
|
||||
For more detailed instructions, see docs/install.txt. |
||||
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
Copyright (c) Django Software Foundation and individual contributors. |
||||
All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, |
||||
are permitted provided that the following conditions are met: |
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, |
||||
this list of conditions and the following disclaimer. |
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright |
||||
notice, this list of conditions and the following disclaimer in the |
||||
documentation and/or other materials provided with the distribution. |
||||
|
||||
3. Neither the name of Django nor the names of its contributors may be used |
||||
to endorse or promote products derived from this software without |
||||
specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR |
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON |
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
|
||||
include README |
||||
include AUTHORS |
||||
include INSTALL |
||||
include LICENSE |
||||
include MANIFEST.in |
||||
recursive-include docs * |
||||
recursive-include scripts * |
||||
recursive-include examples * |
||||
recursive-include extras * |
||||
recursive-include django/conf/locale * |
||||
recursive-include django/contrib/admin/templates * |
||||
recursive-include django/contrib/admin/media * |
||||
recursive-include django/contrib/admindocs/templates * |
||||
recursive-include django/contrib/comments/templates * |
||||
recursive-include django/contrib/databrowse/templates * |
||||
recursive-include django/contrib/sitemaps/templates * |
||||
@ -0,0 +1,38 @@
@@ -0,0 +1,38 @@
|
||||
Django is a high-level Python Web framework that encourages rapid development |
||||
and clean, pragmatic design. |
||||
|
||||
All documentation is in the "docs" directory and online at |
||||
http://docs.djangoproject.com/en/dev/. If you're just getting started, here's |
||||
how we recommend you read the docs: |
||||
|
||||
* First, read docs/intro/install.txt for instructions on installing Django. |
||||
|
||||
* Next, work through the tutorials in order (docs/intro/tutorial01.txt, |
||||
docs/intro/tutorial02.txt, etc.). |
||||
|
||||
* If you want to set up an actual deployment server, read |
||||
docs/howto/deployment/modpython.txt for instructions on running Django |
||||
under mod_python. |
||||
|
||||
* You'll probably want to read through the topical guides (in docs/topics) |
||||
next; from there you can jump to the HOWTOs (in docs/howto) for specific |
||||
problems, and check out the reference (docs/ref) for gory details. |
||||
|
||||
Docs are updated rigorously. If you find any problems in the docs, or think they |
||||
should be clarified in any way, please take 30 seconds to fill out a ticket |
||||
here: |
||||
|
||||
http://code.djangoproject.com/newticket |
||||
|
||||
To get more help: |
||||
|
||||
* Join the #django channel on irc.freenode.net. Lots of helpful people |
||||
hang out there. Read the archives at http://oebfare.com/logger/django/. |
||||
|
||||
* Join the django-users mailing list, or read the archives, at |
||||
http://groups.google.com/group/django-users. |
||||
|
||||
To contribute to Django: |
||||
|
||||
* Check out http://www.djangoproject.com/community/ for information |
||||
about getting involved. |
||||
@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
|
||||
VERSION = (1, 0, 'final') |
||||
|
||||
def get_version(): |
||||
"Returns the version as a human-format string." |
||||
v = '.'.join([str(i) for i in VERSION[:-1]]) |
||||
if VERSION[-1]: |
||||
from django.utils.version import get_svn_revision |
||||
v = '%s-%s-%s' % (v, VERSION[-1], get_svn_revision()) |
||||
return v |
||||
@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env python |
||||
|
||||
if __name__ == "__main__": |
||||
import sys |
||||
name = sys.argv[0] |
||||
args = ' '.join(sys.argv[1:]) |
||||
print >> sys.stderr, "%s has been moved into django-admin.py" % name |
||||
print >> sys.stderr, 'Please run "django-admin.py compilemessages %s" instead.'% args |
||||
print >> sys.stderr |
||||
sys.exit(1) |
||||
|
||||
@ -0,0 +1,13 @@
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env python |
||||
|
||||
""" |
||||
Daily cleanup job. |
||||
|
||||
Can be run as a cronjob to clean out old data from the database (only expired |
||||
sessions at the moment). |
||||
""" |
||||
|
||||
from django.core import management |
||||
|
||||
if __name__ == "__main__": |
||||
management.call_command('cleanup') |
||||
@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env python |
||||
from django.core import management |
||||
|
||||
if __name__ == "__main__": |
||||
management.execute_from_command_line() |
||||
@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env python |
||||
|
||||
if __name__ == "__main__": |
||||
import sys |
||||
name = sys.argv[0] |
||||
args = ' '.join(sys.argv[1:]) |
||||
print >> sys.stderr, "%s has been moved into django-admin.py" % name |
||||
print >> sys.stderr, 'Please run "django-admin.py makemessages %s" instead.'% args |
||||
print >> sys.stderr |
||||
sys.exit(1) |
||||
|
||||
@ -0,0 +1,36 @@
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python |
||||
|
||||
""" |
||||
gather_profile_stats.py /path/to/dir/of/profiles |
||||
|
||||
Note that the aggregated profiles must be read with pstats.Stats, not |
||||
hotshot.stats (the formats are incompatible) |
||||
""" |
||||
|
||||
from hotshot import stats |
||||
import pstats |
||||
import sys, os |
||||
|
||||
def gather_stats(p): |
||||
profiles = {} |
||||
for f in os.listdir(p): |
||||
if f.endswith('.agg.prof'): |
||||
path = f[:-9] |
||||
prof = pstats.Stats(os.path.join(p, f)) |
||||
elif f.endswith('.prof'): |
||||
bits = f.split('.') |
||||
path = ".".join(bits[:-3]) |
||||
prof = stats.load(os.path.join(p, f)) |
||||
else: |
||||
continue |
||||
print "Processing %s" % f |
||||
if path in profiles: |
||||
profiles[path].add(prof) |
||||
else: |
||||
profiles[path] = prof |
||||
os.unlink(os.path.join(p, f)) |
||||
for (path, prof) in profiles.items(): |
||||
prof.dump_stats(os.path.join(p, "%s.agg.prof" % path)) |
||||
|
||||
if __name__ == '__main__': |
||||
gather_stats(sys.argv[1]) |
||||
@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env python |
||||
|
||||
import os |
||||
import sys |
||||
|
||||
def unique_messages(): |
||||
basedir = None |
||||
|
||||
if os.path.isdir(os.path.join('conf', 'locale')): |
||||
basedir = os.path.abspath(os.path.join('conf', 'locale')) |
||||
elif os.path.isdir('locale'): |
||||
basedir = os.path.abspath('locale') |
||||
else: |
||||
print "this script should be run from the django svn tree or your project or app tree" |
||||
sys.exit(1) |
||||
|
||||
for (dirpath, dirnames, filenames) in os.walk(basedir): |
||||
for f in filenames: |
||||
if f.endswith('.po'): |
||||
sys.stderr.write('processing file %s in %s\n' % (f, dirpath)) |
||||
pf = os.path.splitext(os.path.join(dirpath, f))[0] |
||||
cmd = 'msguniq "%s.po"' % pf |
||||
stdout = os.popen(cmd) |
||||
msg = stdout.read() |
||||
open('%s.po' % pf, 'w').write(msg) |
||||
|
||||
if __name__ == "__main__": |
||||
unique_messages() |
||||
@ -0,0 +1,153 @@
@@ -0,0 +1,153 @@
|
||||
""" |
||||
Settings and configuration for Django. |
||||
|
||||
Values will be read from the module specified by the DJANGO_SETTINGS_MODULE environment |
||||
variable, and then from django.conf.global_settings; see the global settings file for |
||||
a list of all possible variables. |
||||
""" |
||||
|
||||
import os |
||||
import time # Needed for Windows |
||||
from django.conf import global_settings |
||||
|
||||
ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE" |
||||
|
||||
class LazySettings(object): |
||||
""" |
||||
A lazy proxy for either global Django settings or a custom settings object. |
||||
The user can manually configure settings prior to using them. Otherwise, |
||||
Django uses the settings module pointed to by DJANGO_SETTINGS_MODULE. |
||||
""" |
||||
def __init__(self): |
||||
# _target must be either None or something that supports attribute |
||||
# access (getattr, hasattr, etc). |
||||
self._target = None |
||||
|
||||
def __getattr__(self, name): |
||||
if self._target is None: |
||||
self._import_settings() |
||||
if name == '__members__': |
||||
# Used to implement dir(obj), for example. |
||||
return self._target.get_all_members() |
||||
return getattr(self._target, name) |
||||
|
||||
def __setattr__(self, name, value): |
||||
if name == '_target': |
||||
# Assign directly to self.__dict__, because otherwise we'd call |
||||
# __setattr__(), which would be an infinite loop. |
||||
self.__dict__['_target'] = value |
||||
else: |
||||
if self._target is None: |
||||
self._import_settings() |
||||
setattr(self._target, name, value) |
||||
|
||||
def _import_settings(self): |
||||
""" |
||||
Load the settings module pointed to by the environment variable. This |
||||
is used the first time we need any settings at all, if the user has not |
||||
previously configured the settings manually. |
||||
""" |
||||
try: |
||||
settings_module = os.environ[ENVIRONMENT_VARIABLE] |
||||
if not settings_module: # If it's set but is an empty string. |
||||
raise KeyError |
||||
except KeyError: |
||||
# NOTE: This is arguably an EnvironmentError, but that causes |
||||
# problems with Python's interactive help. |
||||
raise ImportError("Settings cannot be imported, because environment variable %s is undefined." % ENVIRONMENT_VARIABLE) |
||||
|
||||
self._target = Settings(settings_module) |
||||
|
||||
def configure(self, default_settings=global_settings, **options): |
||||
""" |
||||
Called to manually configure the settings. The 'default_settings' |
||||
parameter sets where to retrieve any unspecified values from (its |
||||
argument must support attribute access (__getattr__)). |
||||
""" |
||||
if self._target != None: |
||||
raise RuntimeError, 'Settings already configured.' |
||||
holder = UserSettingsHolder(default_settings) |
||||
for name, value in options.items(): |
||||
setattr(holder, name, value) |
||||
self._target = holder |
||||
|
||||
def configured(self): |
||||
""" |
||||
Returns True if the settings have already been configured. |
||||
""" |
||||
return bool(self._target) |
||||
configured = property(configured) |
||||
|
||||
class Settings(object): |
||||
def __init__(self, settings_module): |
||||
# update this dict from global settings (but only for ALL_CAPS settings) |
||||
for setting in dir(global_settings): |
||||
if setting == setting.upper(): |
||||
setattr(self, setting, getattr(global_settings, setting)) |
||||
|
||||
# store the settings module in case someone later cares |
||||
self.SETTINGS_MODULE = settings_module |
||||
|
||||
try: |
||||
mod = __import__(self.SETTINGS_MODULE, {}, {}, ['']) |
||||
except ImportError, e: |
||||
raise ImportError, "Could not import settings '%s' (Is it on sys.path? Does it have syntax errors?): %s" % (self.SETTINGS_MODULE, e) |
||||
|
||||
# Settings that should be converted into tuples if they're mistakenly entered |
||||
# as strings. |
||||
tuple_settings = ("INSTALLED_APPS", "TEMPLATE_DIRS") |
||||
|
||||
for setting in dir(mod): |
||||
if setting == setting.upper(): |
||||
setting_value = getattr(mod, setting) |
||||
if setting in tuple_settings and type(setting_value) == str: |
||||
setting_value = (setting_value,) # In case the user forgot the comma. |
||||
setattr(self, setting, setting_value) |
||||
|
||||
# Expand entries in INSTALLED_APPS like "django.contrib.*" to a list |
||||
# of all those apps. |
||||
new_installed_apps = [] |
||||
for app in self.INSTALLED_APPS: |
||||
if app.endswith('.*'): |
||||
appdir = os.path.dirname(__import__(app[:-2], {}, {}, ['']).__file__) |
||||
app_subdirs = os.listdir(appdir) |
||||
app_subdirs.sort() |
||||
for d in app_subdirs: |
||||
if d.isalpha() and os.path.isdir(os.path.join(appdir, d)): |
||||
new_installed_apps.append('%s.%s' % (app[:-2], d)) |
||||
else: |
||||
new_installed_apps.append(app) |
||||
self.INSTALLED_APPS = new_installed_apps |
||||
|
||||
if hasattr(time, 'tzset'): |
||||
# Move the time zone info into os.environ. See ticket #2315 for why |
||||
# we don't do this unconditionally (breaks Windows). |
||||
os.environ['TZ'] = self.TIME_ZONE |
||||
time.tzset() |
||||
|
||||
def get_all_members(self): |
||||
return dir(self) |
||||
|
||||
class UserSettingsHolder(object): |
||||
""" |
||||
Holder for user configured settings. |
||||
""" |
||||
# SETTINGS_MODULE doesn't make much sense in the manually configured |
||||
# (standalone) case. |
||||
SETTINGS_MODULE = None |
||||
|
||||
def __init__(self, default_settings): |
||||
""" |
||||
Requests for configuration variables not in this class are satisfied |
||||
from the module specified in default_settings (if possible). |
||||
""" |
||||
self.default_settings = default_settings |
||||
|
||||
def __getattr__(self, name): |
||||
return getattr(self.default_settings, name) |
||||
|
||||
def get_all_members(self): |
||||
return dir(self) + dir(self.default_settings) |
||||
|
||||
settings = LazySettings() |
||||
|
||||
@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
|
||||
from django.db import models |
||||
|
||||
# Create your models here. |
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
# Create your views here. |
||||
@ -0,0 +1,401 @@
@@ -0,0 +1,401 @@
|
||||
# Default Django settings. Override these with settings in the module |
||||
# pointed-to by the DJANGO_SETTINGS_MODULE environment variable. |
||||
|
||||
# This is defined here as a do-nothing function because we can't import |
||||
# django.utils.translation -- that module depends on the settings. |
||||
gettext_noop = lambda s: s |
||||
|
||||
#################### |
||||
# CORE # |
||||
#################### |
||||
|
||||
DEBUG = False |
||||
TEMPLATE_DEBUG = False |
||||
|
||||
# Whether the framework should propagate raw exceptions rather than catching |
||||
# them. This is useful under some testing siutations and should never be used |
||||
# on a live site. |
||||
DEBUG_PROPAGATE_EXCEPTIONS = False |
||||
|
||||
# Whether to use the "Etag" header. This saves bandwidth but slows down performance. |
||||
USE_ETAGS = False |
||||
|
||||
# People who get code error notifications. |
||||
# In the format (('Full Name', 'email@domain.com'), ('Full Name', 'anotheremail@domain.com')) |
||||
ADMINS = () |
||||
|
||||
# Tuple of IP addresses, as strings, that: |
||||
# * See debug comments, when DEBUG is true |
||||
# * Receive x-headers |
||||
INTERNAL_IPS = () |
||||
|
||||
# Local time zone for this installation. All choices can be found here: |
||||
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name (although not all |
||||
# systems may support all possibilities). |
||||
TIME_ZONE = 'America/Chicago' |
||||
|
||||
# Language code for this installation. All choices can be found here: |
||||
# http://www.i18nguy.com/unicode/language-identifiers.html |
||||
LANGUAGE_CODE = 'en-us' |
||||
|
||||
# Languages we provide translations for, out of the box. The language name |
||||
# should be the utf-8 encoded local name for the language. |
||||
LANGUAGES = ( |
||||
('ar', gettext_noop('Arabic')), |
||||
('bn', gettext_noop('Bengali')), |
||||
('bg', gettext_noop('Bulgarian')), |
||||
('ca', gettext_noop('Catalan')), |
||||
('cs', gettext_noop('Czech')), |
||||
('cy', gettext_noop('Welsh')), |
||||
('da', gettext_noop('Danish')), |
||||
('de', gettext_noop('German')), |
||||
('el', gettext_noop('Greek')), |
||||
('en', gettext_noop('English')), |
||||
('es', gettext_noop('Spanish')), |
||||
('et', gettext_noop('Estonian')), |
||||
('es-ar', gettext_noop('Argentinean Spanish')), |
||||
('eu', gettext_noop('Basque')), |
||||
('fa', gettext_noop('Persian')), |
||||
('fi', gettext_noop('Finnish')), |
||||
('fr', gettext_noop('French')), |
||||
('ga', gettext_noop('Irish')), |
||||
('gl', gettext_noop('Galician')), |
||||
('hu', gettext_noop('Hungarian')), |
||||
('he', gettext_noop('Hebrew')), |
||||
('hr', gettext_noop('Croatian')), |
||||
('is', gettext_noop('Icelandic')), |
||||
('it', gettext_noop('Italian')), |
||||
('ja', gettext_noop('Japanese')), |
||||
('ka', gettext_noop('Georgian')), |
||||
('ko', gettext_noop('Korean')), |
||||
('km', gettext_noop('Khmer')), |
||||
('kn', gettext_noop('Kannada')), |
||||
('lv', gettext_noop('Latvian')), |
||||
('lt', gettext_noop('Lithuanian')), |
||||
('mk', gettext_noop('Macedonian')), |
||||
('nl', gettext_noop('Dutch')), |
||||
('no', gettext_noop('Norwegian')), |
||||
('pl', gettext_noop('Polish')), |
||||
('pt', gettext_noop('Portugese')), |
||||
('pt-br', gettext_noop('Brazilian Portuguese')), |
||||
('ro', gettext_noop('Romanian')), |
||||
('ru', gettext_noop('Russian')), |
||||
('sk', gettext_noop('Slovak')), |
||||
('sl', gettext_noop('Slovenian')), |
||||
('sr', gettext_noop('Serbian')), |
||||
('sv', gettext_noop('Swedish')), |
||||
('ta', gettext_noop('Tamil')), |
||||
('te', gettext_noop('Telugu')), |
||||
('tr', gettext_noop('Turkish')), |
||||
('uk', gettext_noop('Ukrainian')), |
||||
('zh-cn', gettext_noop('Simplified Chinese')), |
||||
('zh-tw', gettext_noop('Traditional Chinese')), |
||||
) |
||||
|
||||
# Languages using BiDi (right-to-left) layout |
||||
LANGUAGES_BIDI = ("he", "ar", "fa") |
||||
|
||||
# If you set this to False, Django will make some optimizations so as not |
||||
# to load the internationalization machinery. |
||||
USE_I18N = True |
||||
LOCALE_PATHS = () |
||||
LANGUAGE_COOKIE_NAME = 'django_language' |
||||
|
||||
# Not-necessarily-technical managers of the site. They get broken link |
||||
# notifications and other various e-mails. |
||||
MANAGERS = ADMINS |
||||
|
||||
# Default content type and charset to use for all HttpResponse objects, if a |
||||
# MIME type isn't manually specified. These are used to construct the |
||||
# Content-Type header. |
||||
DEFAULT_CONTENT_TYPE = 'text/html' |
||||
DEFAULT_CHARSET = 'utf-8' |
||||
|
||||
# Encoding of files read from disk (template and initial SQL files). |
||||
FILE_CHARSET = 'utf-8' |
||||
|
||||
# E-mail address that error messages come from. |
||||
SERVER_EMAIL = 'root@localhost' |
||||
|
||||
# Whether to send broken-link e-mails. |
||||
SEND_BROKEN_LINK_EMAILS = False |
||||
|
||||
# Database connection info. |
||||
DATABASE_ENGINE = '' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. |
||||
DATABASE_NAME = '' # Or path to database file if using sqlite3. |
||||
DATABASE_USER = '' # Not used with sqlite3. |
||||
DATABASE_PASSWORD = '' # Not used with sqlite3. |
||||
DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3. |
||||
DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. |
||||
DATABASE_OPTIONS = {} # Set to empty dictionary for default. |
||||
|
||||
# Host for sending e-mail. |
||||
EMAIL_HOST = 'localhost' |
||||
|
||||
# Port for sending e-mail. |
||||
EMAIL_PORT = 25 |
||||
|
||||
# Optional SMTP authentication information for EMAIL_HOST. |
||||
EMAIL_HOST_USER = '' |
||||
EMAIL_HOST_PASSWORD = '' |
||||
EMAIL_USE_TLS = False |
||||
|
||||
# List of strings representing installed apps. |
||||
INSTALLED_APPS = () |
||||
|
||||
# List of locations of the template source files, in search order. |
||||
TEMPLATE_DIRS = () |
||||
|
||||
# List of callables that know how to import templates from various sources. |
||||
# See the comments in django/core/template/loader.py for interface |
||||
# documentation. |
||||
TEMPLATE_LOADERS = ( |
||||
'django.template.loaders.filesystem.load_template_source', |
||||
'django.template.loaders.app_directories.load_template_source', |
||||
# 'django.template.loaders.eggs.load_template_source', |
||||
) |
||||
|
||||
# List of processors used by RequestContext to populate the context. |
||||
# Each one should be a callable that takes the request object as its |
||||
# only parameter and returns a dictionary to add to the context. |
||||
TEMPLATE_CONTEXT_PROCESSORS = ( |
||||
'django.core.context_processors.auth', |
||||
'django.core.context_processors.debug', |
||||
'django.core.context_processors.i18n', |
||||
'django.core.context_processors.media', |
||||
# 'django.core.context_processors.request', |
||||
) |
||||
|
||||
# Output to use in template system for invalid (e.g. misspelled) variables. |
||||
TEMPLATE_STRING_IF_INVALID = '' |
||||
|
||||
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a |
||||
# trailing slash. |
||||
# Examples: "http://foo.com/media/", "/media/". |
||||
ADMIN_MEDIA_PREFIX = '/media/' |
||||
|
||||
# Default e-mail address to use for various automated correspondence from |
||||
# the site managers. |
||||
DEFAULT_FROM_EMAIL = 'webmaster@localhost' |
||||
|
||||
# Subject-line prefix for email messages send with django.core.mail.mail_admins |
||||
# or ...mail_managers. Make sure to include the trailing space. |
||||
EMAIL_SUBJECT_PREFIX = '[Django] ' |
||||
|
||||
# Whether to append trailing slashes to URLs. |
||||
APPEND_SLASH = True |
||||
|
||||
# Whether to prepend the "www." subdomain to URLs that don't have it. |
||||
PREPEND_WWW = False |
||||
|
||||
# Override the server-derived value of SCRIPT_NAME |
||||
FORCE_SCRIPT_NAME = None |
||||
|
||||
# List of compiled regular expression objects representing User-Agent strings |
||||
# that are not allowed to visit any page, systemwide. Use this for bad |
||||
# robots/crawlers. Here are a few examples: |
||||
# import re |
||||
# DISALLOWED_USER_AGENTS = ( |
||||
# re.compile(r'^NaverBot.*'), |
||||
# re.compile(r'^EmailSiphon.*'), |
||||
# re.compile(r'^SiteSucker.*'), |
||||
# re.compile(r'^sohu-search') |
||||
# ) |
||||
DISALLOWED_USER_AGENTS = () |
||||
|
||||
ABSOLUTE_URL_OVERRIDES = {} |
||||
|
||||
# Tuple of strings representing allowed prefixes for the {% ssi %} tag. |
||||
# Example: ('/home/html', '/var/www') |
||||
ALLOWED_INCLUDE_ROOTS = () |
||||
|
||||
# If this is a admin settings module, this should be a list of |
||||
# settings modules (in the format 'foo.bar.baz') for which this admin |
||||
# is an admin. |
||||
ADMIN_FOR = () |
||||
|
||||
# 404s that may be ignored. |
||||
IGNORABLE_404_STARTS = ('/cgi-bin/', '/_vti_bin', '/_vti_inf') |
||||
IGNORABLE_404_ENDS = ('mail.pl', 'mailform.pl', 'mail.cgi', 'mailform.cgi', 'favicon.ico', '.php') |
||||
|
||||
# A secret key for this particular Django installation. Used in secret-key |
||||
# hashing algorithms. Set this in your settings, or Django will complain |
||||
# loudly. |
||||
SECRET_KEY = '' |
||||
|
||||
# Path to the "jing" executable -- needed to validate XMLFields |
||||
JING_PATH = "/usr/bin/jing" |
||||
|
||||
# Default file storage mechanism that holds media. |
||||
DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage' |
||||
|
||||
# Absolute path to the directory that holds media. |
||||
# Example: "/home/media/media.lawrence.com/" |
||||
MEDIA_ROOT = '' |
||||
|
||||
# URL that handles the media served from MEDIA_ROOT. |
||||
# Example: "http://media.lawrence.com" |
||||
MEDIA_URL = '' |
||||
|
||||
# List of upload handler classes to be applied in order. |
||||
FILE_UPLOAD_HANDLERS = ( |
||||
'django.core.files.uploadhandler.MemoryFileUploadHandler', |
||||
'django.core.files.uploadhandler.TemporaryFileUploadHandler', |
||||
) |
||||
|
||||
# Maximum size, in bytes, of a request before it will be streamed to the |
||||
# file system instead of into memory. |
||||
FILE_UPLOAD_MAX_MEMORY_SIZE = 2621440 # i.e. 2.5 MB |
||||
|
||||
# Directory in which upload streamed files will be temporarily saved. A value of |
||||
# `None` will make Django use the operating system's default temporary directory |
||||
# (i.e. "/tmp" on *nix systems). |
||||
FILE_UPLOAD_TEMP_DIR = None |
||||
|
||||
# The numeric mode to set newly-uploaded files to. The value should be a mode |
||||
# you'd pass directly to os.chmod; see http://docs.python.org/lib/os-file-dir.html. |
||||
FILE_UPLOAD_PERMISSIONS = None |
||||
|
||||
# Default formatting for date objects. See all available format strings here: |
||||
# http://docs.djangoproject.com/en/dev/ref/templates/builtins/#now |
||||
DATE_FORMAT = 'N j, Y' |
||||
|
||||
# Default formatting for datetime objects. See all available format strings here: |
||||
# http://docs.djangoproject.com/en/dev/ref/templates/builtins/#now |
||||
DATETIME_FORMAT = 'N j, Y, P' |
||||
|
||||
# Default formatting for time objects. See all available format strings here: |
||||
# http://docs.djangoproject.com/en/dev/ref/templates/builtins/#now |
||||
TIME_FORMAT = 'P' |
||||
|
||||
# Default formatting for date objects when only the year and month are relevant. |
||||
# See all available format strings here: |
||||
# http://docs.djangoproject.com/en/dev/ref/templates/builtins/#now |
||||
YEAR_MONTH_FORMAT = 'F Y' |
||||
|
||||
# Default formatting for date objects when only the month and day are relevant. |
||||
# See all available format strings here: |
||||
# http://docs.djangoproject.com/en/dev/ref/templates/builtins/#now |
||||
MONTH_DAY_FORMAT = 'F j' |
||||
|
||||
# Do you want to manage transactions manually? |
||||
# Hint: you really don't! |
||||
TRANSACTIONS_MANAGED = False |
||||
|
||||
# The User-Agent string to use when checking for URL validity through the |
||||
# isExistingURL validator. |
||||
from django import get_version |
||||
URL_VALIDATOR_USER_AGENT = "Django/%s (http://www.djangoproject.com)" % get_version() |
||||
|
||||
# The tablespaces to use for each model when not specified otherwise. |
||||
DEFAULT_TABLESPACE = '' |
||||
DEFAULT_INDEX_TABLESPACE = '' |
||||
|
||||
############## |
||||
# MIDDLEWARE # |
||||
############## |
||||
|
||||
# List of middleware classes to use. Order is important; in the request phase, |
||||
# this middleware classes will be applied in the order given, and in the |
||||
# response phase the middleware will be applied in reverse order. |
||||
MIDDLEWARE_CLASSES = ( |
||||
'django.contrib.sessions.middleware.SessionMiddleware', |
||||
'django.contrib.auth.middleware.AuthenticationMiddleware', |
||||
# 'django.middleware.http.ConditionalGetMiddleware', |
||||
# 'django.middleware.gzip.GZipMiddleware', |
||||
'django.middleware.common.CommonMiddleware', |
||||
) |
||||
|
||||
############ |
||||
# SESSIONS # |
||||
############ |
||||
|
||||
SESSION_COOKIE_NAME = 'sessionid' # Cookie name. This can be whatever you want. |
||||
SESSION_COOKIE_AGE = 60 * 60 * 24 * 7 * 2 # Age of cookie, in seconds (default: 2 weeks). |
||||
SESSION_COOKIE_DOMAIN = None # A string like ".lawrence.com", or None for standard domain cookie. |
||||
SESSION_COOKIE_SECURE = False # Whether the session cookie should be secure (https:// only). |
||||
SESSION_COOKIE_PATH = '/' # The path of the session cookie. |
||||
SESSION_SAVE_EVERY_REQUEST = False # Whether to save the session data on every request. |
||||
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # Whether a user's session cookie expires when the Web browser is closed. |
||||
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # The module to store session data |
||||
SESSION_FILE_PATH = None # Directory to store session files if using the file session module. If None, the backend will use a sensible default. |
||||
|
||||
######### |
||||
# CACHE # |
||||
######### |
||||
|
||||
# The cache backend to use. See the docstring in django.core.cache for the |
||||
# possible values. |
||||
CACHE_BACKEND = 'locmem://' |
||||
CACHE_MIDDLEWARE_KEY_PREFIX = '' |
||||
CACHE_MIDDLEWARE_SECONDS = 600 |
||||
|
||||
#################### |
||||
# COMMENTS # |
||||
#################### |
||||
|
||||
COMMENTS_ALLOW_PROFANITIES = False |
||||
|
||||
# The profanities that will trigger a validation error in the |
||||
# 'hasNoProfanities' validator. All of these should be in lowercase. |
||||
PROFANITIES_LIST = ('asshat', 'asshead', 'asshole', 'cunt', 'fuck', 'gook', 'nigger', 'shit') |
||||
|
||||
# The group ID that designates which users are banned. |
||||
# Set to None if you're not using it. |
||||
COMMENTS_BANNED_USERS_GROUP = None |
||||
|
||||
# The group ID that designates which users can moderate comments. |
||||
# Set to None if you're not using it. |
||||
COMMENTS_MODERATORS_GROUP = None |
||||
|
||||
# The group ID that designates the users whose comments should be e-mailed to MANAGERS. |
||||
# Set to None if you're not using it. |
||||
COMMENTS_SKETCHY_USERS_GROUP = None |
||||
|
||||
# The system will e-mail MANAGERS the first COMMENTS_FIRST_FEW comments by each |
||||
# user. Set this to 0 if you want to disable it. |
||||
COMMENTS_FIRST_FEW = 0 |
||||
|
||||
# A tuple of IP addresses that have been banned from participating in various |
||||
# Django-powered features. |
||||
BANNED_IPS = () |
||||
|
||||
################## |
||||
# AUTHENTICATION # |
||||
################## |
||||
|
||||
AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',) |
||||
|
||||
LOGIN_URL = '/accounts/login/' |
||||
|
||||
LOGOUT_URL = '/accounts/logout/' |
||||
|
||||
LOGIN_REDIRECT_URL = '/accounts/profile/' |
||||
|
||||
# The number of days a password reset link is valid for |
||||
PASSWORD_RESET_TIMEOUT_DAYS = 3 |
||||
|
||||
########### |
||||
# TESTING # |
||||
########### |
||||
|
||||
# The name of the method to use to invoke the test suite |
||||
TEST_RUNNER = 'django.test.simple.run_tests' |
||||
|
||||
# The name of the database to use for testing purposes. |
||||
# If None, a name of 'test_' + DATABASE_NAME will be assumed |
||||
TEST_DATABASE_NAME = None |
||||
|
||||
# Strings used to set the character set and collation order for the test |
||||
# database. These values are passed literally to the server, so they are |
||||
# backend-dependent. If None, no special settings are sent (system defaults are |
||||
# used). |
||||
TEST_DATABASE_CHARSET = None |
||||
TEST_DATABASE_COLLATION = None |
||||
|
||||
############ |
||||
# FIXTURES # |
||||
############ |
||||
|
||||
# The list of directories to search for fixtures |
||||
FIXTURE_DIRS = () |
||||
@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
|
||||
#!/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) |
||||
@ -0,0 +1,79 @@
@@ -0,0 +1,79 @@
|
||||
# Django settings for {{ project_name }} project. |
||||
|
||||
DEBUG = True |
||||
TEMPLATE_DEBUG = DEBUG |
||||
|
||||
ADMINS = ( |
||||
# ('Your Name', 'your_email@domain.com'), |
||||
) |
||||
|
||||
MANAGERS = ADMINS |
||||
|
||||
DATABASE_ENGINE = '' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. |
||||
DATABASE_NAME = '' # Or path to database file if using sqlite3. |
||||
DATABASE_USER = '' # Not used with sqlite3. |
||||
DATABASE_PASSWORD = '' # Not used with sqlite3. |
||||
DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3. |
||||
DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. |
||||
|
||||
# Local time zone for this installation. Choices can be found here: |
||||
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name |
||||
# although not all choices may be available on all operating systems. |
||||
# If running in a Windows environment this must be set to the same as your |
||||
# system time zone. |
||||
TIME_ZONE = 'America/Chicago' |
||||
|
||||
# Language code for this installation. All choices can be found here: |
||||
# http://www.i18nguy.com/unicode/language-identifiers.html |
||||
LANGUAGE_CODE = 'en-us' |
||||
|
||||
SITE_ID = 1 |
||||
|
||||
# If you set this to False, Django will make some optimizations so as not |
||||
# to load the internationalization machinery. |
||||
USE_I18N = True |
||||
|
||||
# Absolute path to the directory that holds media. |
||||
# Example: "/home/media/media.lawrence.com/" |
||||
MEDIA_ROOT = '' |
||||
|
||||
# URL that handles the media served from MEDIA_ROOT. Make sure to use a |
||||
# trailing slash if there is a path component (optional in other cases). |
||||
# Examples: "http://media.lawrence.com", "http://example.com/media/" |
||||
MEDIA_URL = '' |
||||
|
||||
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a |
||||
# trailing slash. |
||||
# Examples: "http://foo.com/media/", "/media/". |
||||
ADMIN_MEDIA_PREFIX = '/media/' |
||||
|
||||
# Make this unique, and don't share it with anybody. |
||||
SECRET_KEY = '' |
||||
|
||||
# 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', |
||||
# 'django.template.loaders.eggs.load_template_source', |
||||
) |
||||
|
||||
MIDDLEWARE_CLASSES = ( |
||||
'django.middleware.common.CommonMiddleware', |
||||
'django.contrib.sessions.middleware.SessionMiddleware', |
||||
'django.contrib.auth.middleware.AuthenticationMiddleware', |
||||
) |
||||
|
||||
ROOT_URLCONF = '{{ project_name }}.urls' |
||||
|
||||
TEMPLATE_DIRS = ( |
||||
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". |
||||
# Always use forward slashes, even on Windows. |
||||
# Don't forget to use absolute paths, not relative paths. |
||||
) |
||||
|
||||
INSTALLED_APPS = ( |
||||
'django.contrib.auth', |
||||
'django.contrib.contenttypes', |
||||
'django.contrib.sessions', |
||||
'django.contrib.sites', |
||||
) |
||||
@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
|
||||
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'^{{ project_name }}/', include('{{ project_name }}.foo.urls')), |
||||
|
||||
# Uncomment the admin/doc line below and add 'django.contrib.admindocs' |
||||
# to INSTALLED_APPS to enable admin documentation: |
||||
# (r'^admin/doc/', include('django.contrib.admindocs.urls')), |
||||
|
||||
# Uncomment the next line to enable the admin: |
||||
# (r'^admin/(.*)', admin.site.root), |
||||
) |
||||
@ -0,0 +1,32 @@
@@ -0,0 +1,32 @@
|
||||
from django.core.urlresolvers import RegexURLPattern, RegexURLResolver |
||||
from django.core.exceptions import ImproperlyConfigured |
||||
|
||||
__all__ = ['handler404', 'handler500', 'include', 'patterns', 'url'] |
||||
|
||||
handler404 = 'django.views.defaults.page_not_found' |
||||
handler500 = 'django.views.defaults.server_error' |
||||
|
||||
include = lambda urlconf_module: [urlconf_module] |
||||
|
||||
def patterns(prefix, *args): |
||||
pattern_list = [] |
||||
for t in args: |
||||
if isinstance(t, (list, tuple)): |
||||
t = url(prefix=prefix, *t) |
||||
elif isinstance(t, RegexURLPattern): |
||||
t.add_prefix(prefix) |
||||
pattern_list.append(t) |
||||
return pattern_list |
||||
|
||||
def url(regex, view, kwargs=None, name=None, prefix=''): |
||||
if type(view) == list: |
||||
# For include(...) processing. |
||||
return RegexURLResolver(regex, view[0], kwargs) |
||||
else: |
||||
if isinstance(view, basestring): |
||||
if not view: |
||||
raise ImproperlyConfigured('Empty URL pattern view name not permitted (for pattern %r)' % regex) |
||||
if prefix: |
||||
view = prefix + '.' + view |
||||
return RegexURLPattern(regex, view, kwargs, name) |
||||
|
||||
@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
|
||||
from django.conf.urls.defaults import * |
||||
|
||||
urlpatterns = patterns('', |
||||
(r'^setlang/$', 'django.views.i18n.set_language'), |
||||
) |
||||
@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
|
||||
from django.conf.urls.defaults import * |
||||
|
||||
urlpatterns = patterns('django.views', |
||||
(r'^(?P<content_type_id>\d+)/(?P<object_id>.*)/$', 'defaults.shortcut'), |
||||
) |
||||
@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
|
||||
from django.contrib.admin.options import ModelAdmin, HORIZONTAL, VERTICAL |
||||
from django.contrib.admin.options import StackedInline, TabularInline |
||||
from django.contrib.admin.sites import AdminSite, site |
||||
|
||||
def autodiscover(): |
||||
""" |
||||
Auto-discover INSTALLED_APPS admin.py modules and fail silently when |
||||
not present. This forces an import on them to register any admin bits they |
||||
may want. |
||||
""" |
||||
import imp |
||||
from django.conf import settings |
||||
|
||||
for app in settings.INSTALLED_APPS: |
||||
# For each app, we need to look for an admin.py inside that app's |
||||
# package. We can't use os.path here -- recall that modules may be |
||||
# imported different ways (think zip files) -- so we need to get |
||||
# the app's __path__ and look for admin.py on that path. |
||||
|
||||
# Step 1: find out the app's __path__ Import errors here will (and |
||||
# should) bubble up, but a missing __path__ (which is legal, but weird) |
||||
# fails silently -- apps that do weird things with __path__ might |
||||
# need to roll their own admin registration. |
||||
try: |
||||
app_path = __import__(app, {}, {}, [app.split('.')[-1]]).__path__ |
||||
except AttributeError: |
||||
continue |
||||
|
||||
# Step 2: use imp.find_module to find the app's admin.py. For some |
||||
# reason imp.find_module raises ImportError if the app can't be found |
||||
# but doesn't actually try to import the module. So skip this app if |
||||
# its admin.py doesn't exist |
||||
try: |
||||
imp.find_module('admin', app_path) |
||||
except ImportError: |
||||
continue |
||||
|
||||
# Step 3: import the app's admin file. If this has errors we want them |
||||
# to bubble up. |
||||
__import__("%s.admin" % app) |
||||
@ -0,0 +1,179 @@
@@ -0,0 +1,179 @@
|
||||
""" |
||||
FilterSpec encapsulates the logic for displaying filters in the Django admin. |
||||
Filters are specified in models with the "list_filter" option. |
||||
|
||||
Each filter subclass knows how to display a filter for a field that passes a |
||||
certain test -- e.g. being a DateField or ForeignKey. |
||||
""" |
||||
|
||||
from django.db import models |
||||
from django.utils.encoding import smart_unicode, iri_to_uri |
||||
from django.utils.translation import ugettext as _ |
||||
from django.utils.html import escape |
||||
from django.utils.safestring import mark_safe |
||||
import datetime |
||||
|
||||
class FilterSpec(object): |
||||
filter_specs = [] |
||||
def __init__(self, f, request, params, model, model_admin): |
||||
self.field = f |
||||
self.params = params |
||||
|
||||
def register(cls, test, factory): |
||||
cls.filter_specs.append((test, factory)) |
||||
register = classmethod(register) |
||||
|
||||
def create(cls, f, request, params, model, model_admin): |
||||
for test, factory in cls.filter_specs: |
||||
if test(f): |
||||
return factory(f, request, params, model, model_admin) |
||||
create = classmethod(create) |
||||
|
||||
def has_output(self): |
||||
return True |
||||
|
||||
def choices(self, cl): |
||||
raise NotImplementedError() |
||||
|
||||
def title(self): |
||||
return self.field.verbose_name |
||||
|
||||
def output(self, cl): |
||||
t = [] |
||||
if self.has_output(): |
||||
t.append(_(u'<h3>By %s:</h3>\n<ul>\n') % escape(self.title())) |
||||
|
||||
for choice in self.choices(cl): |
||||
t.append(u'<li%s><a href="%s">%s</a></li>\n' % \ |
||||
((choice['selected'] and ' class="selected"' or ''), |
||||
iri_to_uri(choice['query_string']), |
||||
choice['display'])) |
||||
t.append('</ul>\n\n') |
||||
return mark_safe("".join(t)) |
||||
|
||||
class RelatedFilterSpec(FilterSpec): |
||||
def __init__(self, f, request, params, model, model_admin): |
||||
super(RelatedFilterSpec, self).__init__(f, request, params, model, model_admin) |
||||
if isinstance(f, models.ManyToManyField): |
||||
self.lookup_title = f.rel.to._meta.verbose_name |
||||
else: |
||||
self.lookup_title = f.verbose_name |
||||
self.lookup_kwarg = '%s__%s__exact' % (f.name, f.rel.to._meta.pk.name) |
||||
self.lookup_val = request.GET.get(self.lookup_kwarg, None) |
||||
self.lookup_choices = f.rel.to._default_manager.all() |
||||
|
||||
def has_output(self): |
||||
return len(self.lookup_choices) > 1 |
||||
|
||||
def title(self): |
||||
return self.lookup_title |
||||
|
||||
def choices(self, cl): |
||||
yield {'selected': self.lookup_val is None, |
||||
'query_string': cl.get_query_string({}, [self.lookup_kwarg]), |
||||
'display': _('All')} |
||||
for val in self.lookup_choices: |
||||
pk_val = getattr(val, self.field.rel.to._meta.pk.attname) |
||||
yield {'selected': self.lookup_val == smart_unicode(pk_val), |
||||
'query_string': cl.get_query_string({self.lookup_kwarg: pk_val}), |
||||
'display': val} |
||||
|
||||
FilterSpec.register(lambda f: bool(f.rel), RelatedFilterSpec) |
||||
|
||||
class ChoicesFilterSpec(FilterSpec): |
||||
def __init__(self, f, request, params, model, model_admin): |
||||
super(ChoicesFilterSpec, self).__init__(f, request, params, model, model_admin) |
||||
self.lookup_kwarg = '%s__exact' % f.name |
||||
self.lookup_val = request.GET.get(self.lookup_kwarg, None) |
||||
|
||||
def choices(self, cl): |
||||
yield {'selected': self.lookup_val is None, |
||||
'query_string': cl.get_query_string({}, [self.lookup_kwarg]), |
||||
'display': _('All')} |
||||
for k, v in self.field.choices: |
||||
yield {'selected': smart_unicode(k) == self.lookup_val, |
||||
'query_string': cl.get_query_string({self.lookup_kwarg: k}), |
||||
'display': v} |
||||
|
||||
FilterSpec.register(lambda f: bool(f.choices), ChoicesFilterSpec) |
||||
|
||||
class DateFieldFilterSpec(FilterSpec): |
||||
def __init__(self, f, request, params, model, model_admin): |
||||
super(DateFieldFilterSpec, self).__init__(f, request, params, model, model_admin) |
||||
|
||||
self.field_generic = '%s__' % self.field.name |
||||
|
||||
self.date_params = dict([(k, v) for k, v in params.items() if k.startswith(self.field_generic)]) |
||||
|
||||
today = datetime.date.today() |
||||
one_week_ago = today - datetime.timedelta(days=7) |
||||
today_str = isinstance(self.field, models.DateTimeField) and today.strftime('%Y-%m-%d 23:59:59') or today.strftime('%Y-%m-%d') |
||||
|
||||
self.links = ( |
||||
(_('Any date'), {}), |
||||
(_('Today'), {'%s__year' % self.field.name: str(today.year), |
||||
'%s__month' % self.field.name: str(today.month), |
||||
'%s__day' % self.field.name: str(today.day)}), |
||||
(_('Past 7 days'), {'%s__gte' % self.field.name: one_week_ago.strftime('%Y-%m-%d'), |
||||
'%s__lte' % f.name: today_str}), |
||||
(_('This month'), {'%s__year' % self.field.name: str(today.year), |
||||
'%s__month' % f.name: str(today.month)}), |
||||
(_('This year'), {'%s__year' % self.field.name: str(today.year)}) |
||||
) |
||||
|
||||
def title(self): |
||||
return self.field.verbose_name |
||||
|
||||
def choices(self, cl): |
||||
for title, param_dict in self.links: |
||||
yield {'selected': self.date_params == param_dict, |
||||
'query_string': cl.get_query_string(param_dict, [self.field_generic]), |
||||
'display': title} |
||||
|
||||
FilterSpec.register(lambda f: isinstance(f, models.DateField), DateFieldFilterSpec) |
||||
|
||||
class BooleanFieldFilterSpec(FilterSpec): |
||||
def __init__(self, f, request, params, model, model_admin): |
||||
super(BooleanFieldFilterSpec, self).__init__(f, request, params, model, model_admin) |
||||
self.lookup_kwarg = '%s__exact' % f.name |
||||
self.lookup_kwarg2 = '%s__isnull' % f.name |
||||
self.lookup_val = request.GET.get(self.lookup_kwarg, None) |
||||
self.lookup_val2 = request.GET.get(self.lookup_kwarg2, None) |
||||
|
||||
def title(self): |
||||
return self.field.verbose_name |
||||
|
||||
def choices(self, cl): |
||||
for k, v in ((_('All'), None), (_('Yes'), '1'), (_('No'), '0')): |
||||
yield {'selected': self.lookup_val == v and not self.lookup_val2, |
||||
'query_string': cl.get_query_string({self.lookup_kwarg: v}, [self.lookup_kwarg2]), |
||||
'display': k} |
||||
if isinstance(self.field, models.NullBooleanField): |
||||
yield {'selected': self.lookup_val2 == 'True', |
||||
'query_string': cl.get_query_string({self.lookup_kwarg2: 'True'}, [self.lookup_kwarg]), |
||||
'display': _('Unknown')} |
||||
|
||||
FilterSpec.register(lambda f: isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField), BooleanFieldFilterSpec) |
||||
|
||||
# This should be registered last, because it's a last resort. For example, |
||||
# if a field is eligible to use the BooleanFieldFilterSpec, that'd be much |
||||
# more appropriate, and the AllValuesFilterSpec won't get used for it. |
||||
class AllValuesFilterSpec(FilterSpec): |
||||
def __init__(self, f, request, params, model, model_admin): |
||||
super(AllValuesFilterSpec, self).__init__(f, request, params, model, model_admin) |
||||
self.lookup_val = request.GET.get(f.name, None) |
||||
self.lookup_choices = model_admin.queryset(request).distinct().order_by(f.name).values(f.name) |
||||
|
||||
def title(self): |
||||
return self.field.verbose_name |
||||
|
||||
def choices(self, cl): |
||||
yield {'selected': self.lookup_val is None, |
||||
'query_string': cl.get_query_string({}, [self.field.name]), |
||||
'display': _('All')} |
||||
for val in self.lookup_choices: |
||||
val = smart_unicode(val[self.field.name]) |
||||
yield {'selected': self.lookup_val == val, |
||||
'query_string': cl.get_query_string({self.field.name: val}), |
||||
'display': val} |
||||
FilterSpec.register(lambda f: True, AllValuesFilterSpec) |
||||
@ -0,0 +1,154 @@
@@ -0,0 +1,154 @@
|
||||
|
||||
from django import forms |
||||
from django.conf import settings |
||||
from django.utils.html import escape |
||||
from django.utils.safestring import mark_safe |
||||
from django.utils.encoding import force_unicode |
||||
from django.contrib.admin.util import flatten_fieldsets |
||||
from django.contrib.contenttypes.models import ContentType |
||||
|
||||
class AdminForm(object): |
||||
def __init__(self, form, fieldsets, prepopulated_fields): |
||||
self.form, self.fieldsets = form, fieldsets |
||||
self.prepopulated_fields = [{ |
||||
'field': form[field_name], |
||||
'dependencies': [form[f] for f in dependencies] |
||||
} for field_name, dependencies in prepopulated_fields.items()] |
||||
|
||||
def __iter__(self): |
||||
for name, options in self.fieldsets: |
||||
yield Fieldset(self.form, name, **options) |
||||
|
||||
def first_field(self): |
||||
try: |
||||
fieldset_name, fieldset_options = self.fieldsets[0] |
||||
field_name = fieldset_options['fields'][0] |
||||
if not isinstance(field_name, basestring): |
||||
field_name = field_name[0] |
||||
return self.form[field_name] |
||||
except (KeyError, IndexError): |
||||
pass |
||||
try: |
||||
return iter(self.form).next() |
||||
except StopIteration: |
||||
return None |
||||
|
||||
def _media(self): |
||||
media = self.form.media |
||||
for fs in self: |
||||
media = media + fs.media |
||||
return media |
||||
media = property(_media) |
||||
|
||||
class Fieldset(object): |
||||
def __init__(self, form, name=None, fields=(), classes=(), description=None): |
||||
self.form = form |
||||
self.name, self.fields = name, fields |
||||
self.classes = u' '.join(classes) |
||||
self.description = description |
||||
|
||||
def _media(self): |
||||
if 'collapse' in self.classes: |
||||
return forms.Media(js=['%sjs/admin/CollapsedFieldsets.js' % settings.ADMIN_MEDIA_PREFIX]) |
||||
return forms.Media() |
||||
media = property(_media) |
||||
|
||||
def __iter__(self): |
||||
for field in self.fields: |
||||
yield Fieldline(self.form, field) |
||||
|
||||
class Fieldline(object): |
||||
def __init__(self, form, field): |
||||
self.form = form # A django.forms.Form instance |
||||
if isinstance(field, basestring): |
||||
self.fields = [field] |
||||
else: |
||||
self.fields = field |
||||
|
||||
def __iter__(self): |
||||
for i, field in enumerate(self.fields): |
||||
yield AdminField(self.form, field, is_first=(i == 0)) |
||||
|
||||
def errors(self): |
||||
return mark_safe(u'\n'.join([self.form[f].errors.as_ul() for f in self.fields]).strip('\n')) |
||||
|
||||
class AdminField(object): |
||||
def __init__(self, form, field, is_first): |
||||
self.field = form[field] # A django.forms.BoundField instance |
||||
self.is_first = is_first # Whether this field is first on the line |
||||
self.is_checkbox = isinstance(self.field.field.widget, forms.CheckboxInput) |
||||
|
||||
def label_tag(self): |
||||
classes = [] |
||||
if self.is_checkbox: |
||||
classes.append(u'vCheckboxLabel') |
||||
contents = force_unicode(escape(self.field.label)) |
||||
else: |
||||
contents = force_unicode(escape(self.field.label)) + u':' |
||||
if self.field.field.required: |
||||
classes.append(u'required') |
||||
if not self.is_first: |
||||
classes.append(u'inline') |
||||
attrs = classes and {'class': u' '.join(classes)} or {} |
||||
return self.field.label_tag(contents=contents, attrs=attrs) |
||||
|
||||
class InlineAdminFormSet(object): |
||||
""" |
||||
A wrapper around an inline formset for use in the admin system. |
||||
""" |
||||
def __init__(self, inline, formset, fieldsets): |
||||
self.opts = inline |
||||
self.formset = formset |
||||
self.fieldsets = fieldsets |
||||
|
||||
def __iter__(self): |
||||
for form, original in zip(self.formset.initial_forms, self.formset.get_queryset()): |
||||
yield InlineAdminForm(self.formset, form, self.fieldsets, self.opts.prepopulated_fields, original) |
||||
for form in self.formset.extra_forms: |
||||
yield InlineAdminForm(self.formset, form, self.fieldsets, self.opts.prepopulated_fields, None) |
||||
|
||||
def fields(self): |
||||
for field_name in flatten_fieldsets(self.fieldsets): |
||||
yield self.formset.form.base_fields[field_name] |
||||
|
||||
def _media(self): |
||||
media = self.opts.media + self.formset.media |
||||
for fs in self: |
||||
media = media + fs.media |
||||
return media |
||||
media = property(_media) |
||||
|
||||
class InlineAdminForm(AdminForm): |
||||
""" |
||||
A wrapper around an inline form for use in the admin system. |
||||
""" |
||||
def __init__(self, formset, form, fieldsets, prepopulated_fields, original): |
||||
self.formset = formset |
||||
self.original = original |
||||
if original is not None: |
||||
self.original.content_type_id = ContentType.objects.get_for_model(original).pk |
||||
self.show_url = original and hasattr(original, 'get_absolute_url') |
||||
super(InlineAdminForm, self).__init__(form, fieldsets, prepopulated_fields) |
||||
|
||||
def pk_field(self): |
||||
return AdminField(self.form, self.formset._pk_field.name, False) |
||||
|
||||
def deletion_field(self): |
||||
from django.forms.formsets import DELETION_FIELD_NAME |
||||
return AdminField(self.form, DELETION_FIELD_NAME, False) |
||||
|
||||
def ordering_field(self): |
||||
from django.forms.formsets import ORDERING_FIELD_NAME |
||||
return AdminField(self.form, ORDERING_FIELD_NAME, False) |
||||
|
||||
class AdminErrorList(forms.util.ErrorList): |
||||
""" |
||||
Stores all errors for the form/formsets in an add/change stage view. |
||||
""" |
||||
def __init__(self, form, inline_formsets): |
||||
if form.is_bound: |
||||
self.extend(form.errors.values()) |
||||
for inline_formset in inline_formsets: |
||||
self.extend(inline_formset.non_form_errors()) |
||||
for errors_in_inline_form in inline_formset.errors: |
||||
self.extend(errors_in_inline_form.values()) |
||||
@ -0,0 +1,54 @@
@@ -0,0 +1,54 @@
|
||||
from django.db import models |
||||
from django.contrib.contenttypes.models import ContentType |
||||
from django.contrib.auth.models import User |
||||
from django.contrib.admin.util import quote |
||||
from django.utils.translation import ugettext_lazy as _ |
||||
from django.utils.encoding import smart_unicode |
||||
from django.utils.safestring import mark_safe |
||||
|
||||
ADDITION = 1 |
||||
CHANGE = 2 |
||||
DELETION = 3 |
||||
|
||||
class LogEntryManager(models.Manager): |
||||
def log_action(self, user_id, content_type_id, object_id, object_repr, action_flag, change_message=''): |
||||
e = self.model(None, None, user_id, content_type_id, smart_unicode(object_id), object_repr[:200], action_flag, change_message) |
||||
e.save() |
||||
|
||||
class LogEntry(models.Model): |
||||
action_time = models.DateTimeField(_('action time'), auto_now=True) |
||||
user = models.ForeignKey(User) |
||||
content_type = models.ForeignKey(ContentType, blank=True, null=True) |
||||
object_id = models.TextField(_('object id'), blank=True, null=True) |
||||
object_repr = models.CharField(_('object repr'), max_length=200) |
||||
action_flag = models.PositiveSmallIntegerField(_('action flag')) |
||||
change_message = models.TextField(_('change message'), blank=True) |
||||
objects = LogEntryManager() |
||||
class Meta: |
||||
verbose_name = _('log entry') |
||||
verbose_name_plural = _('log entries') |
||||
db_table = 'django_admin_log' |
||||
ordering = ('-action_time',) |
||||
|
||||
def __repr__(self): |
||||
return smart_unicode(self.action_time) |
||||
|
||||
def is_addition(self): |
||||
return self.action_flag == ADDITION |
||||
|
||||
def is_change(self): |
||||
return self.action_flag == CHANGE |
||||
|
||||
def is_deletion(self): |
||||
return self.action_flag == DELETION |
||||
|
||||
def get_edited_object(self): |
||||
"Returns the edited object represented by this log entry" |
||||
return self.content_type.get_object_for_this_type(pk=self.object_id) |
||||
|
||||
def get_admin_url(self): |
||||
""" |
||||
Returns the admin URL to edit the object represented by this log entry. |
||||
This is relative to the Django admin index page. |
||||
""" |
||||
return mark_safe(u"%s/%s/%s/" % (self.content_type.app_label, self.content_type.model, quote(self.object_id))) |
||||
@ -0,0 +1,816 @@
@@ -0,0 +1,816 @@
|
||||
from django import forms, template |
||||
from django.forms.formsets import all_valid |
||||
from django.forms.models import modelform_factory, inlineformset_factory |
||||
from django.forms.models import BaseInlineFormSet |
||||
from django.contrib.contenttypes.models import ContentType |
||||
from django.contrib.admin import widgets |
||||
from django.contrib.admin import helpers |
||||
from django.contrib.admin.util import quote, unquote, flatten_fieldsets, get_deleted_objects |
||||
from django.core.exceptions import PermissionDenied |
||||
from django.db import models, transaction |
||||
from django.http import Http404, HttpResponse, HttpResponseRedirect |
||||
from django.shortcuts import get_object_or_404, render_to_response |
||||
from django.utils.html import escape |
||||
from django.utils.safestring import mark_safe |
||||
from django.utils.text import capfirst, get_text_list |
||||
from django.utils.translation import ugettext as _ |
||||
from django.utils.encoding import force_unicode |
||||
try: |
||||
set |
||||
except NameError: |
||||
from sets import Set as set # Python 2.3 fallback |
||||
|
||||
HORIZONTAL, VERTICAL = 1, 2 |
||||
# returns the <ul> class for a given radio_admin field |
||||
get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '') |
||||
|
||||
class IncorrectLookupParameters(Exception): |
||||
pass |
||||
|
||||
class BaseModelAdmin(object): |
||||
"""Functionality common to both ModelAdmin and InlineAdmin.""" |
||||
raw_id_fields = () |
||||
fields = None |
||||
exclude = None |
||||
fieldsets = None |
||||
form = forms.ModelForm |
||||
filter_vertical = () |
||||
filter_horizontal = () |
||||
radio_fields = {} |
||||
prepopulated_fields = {} |
||||
|
||||
def formfield_for_dbfield(self, db_field, **kwargs): |
||||
""" |
||||
Hook for specifying the form Field instance for a given database Field |
||||
instance. |
||||
|
||||
If kwargs are given, they're passed to the form Field's constructor. |
||||
""" |
||||
|
||||
# If the field specifies choices, we don't need to look for special |
||||
# admin widgets - we just need to use a select widget of some kind. |
||||
if db_field.choices: |
||||
if db_field.name in self.radio_fields: |
||||
# If the field is named as a radio_field, use a RadioSelect |
||||
kwargs['widget'] = widgets.AdminRadioSelect(attrs={ |
||||
'class': get_ul_class(self.radio_fields[db_field.name]), |
||||
}) |
||||
kwargs['choices'] = db_field.get_choices( |
||||
include_blank = db_field.blank, |
||||
blank_choice=[('', _('None'))] |
||||
) |
||||
return db_field.formfield(**kwargs) |
||||
else: |
||||
# Otherwise, use the default select widget. |
||||
return db_field.formfield(**kwargs) |
||||
|
||||
# For DateTimeFields, use a special field and widget. |
||||
if isinstance(db_field, models.DateTimeField): |
||||
kwargs['form_class'] = forms.SplitDateTimeField |
||||
kwargs['widget'] = widgets.AdminSplitDateTime() |
||||
return db_field.formfield(**kwargs) |
||||
|
||||
# For DateFields, add a custom CSS class. |
||||
if isinstance(db_field, models.DateField): |
||||
kwargs['widget'] = widgets.AdminDateWidget |
||||
return db_field.formfield(**kwargs) |
||||
|
||||
# For TimeFields, add a custom CSS class. |
||||
if isinstance(db_field, models.TimeField): |
||||
kwargs['widget'] = widgets.AdminTimeWidget |
||||
return db_field.formfield(**kwargs) |
||||
|
||||
# For TextFields, add a custom CSS class. |
||||
if isinstance(db_field, models.TextField): |
||||
kwargs['widget'] = widgets.AdminTextareaWidget |
||||
return db_field.formfield(**kwargs) |
||||
|
||||
# For URLFields, add a custom CSS class. |
||||
if isinstance(db_field, models.URLField): |
||||
kwargs['widget'] = widgets.AdminURLFieldWidget |
||||
return db_field.formfield(**kwargs) |
||||
|
||||
# For IntegerFields, add a custom CSS class. |
||||
if isinstance(db_field, models.IntegerField): |
||||
kwargs['widget'] = widgets.AdminIntegerFieldWidget |
||||
return db_field.formfield(**kwargs) |
||||
|
||||
# For CommaSeparatedIntegerFields, add a custom CSS class. |
||||
if isinstance(db_field, models.CommaSeparatedIntegerField): |
||||
kwargs['widget'] = widgets.AdminCommaSeparatedIntegerFieldWidget |
||||
return db_field.formfield(**kwargs) |
||||
|
||||
# For TextInputs, add a custom CSS class. |
||||
if isinstance(db_field, models.CharField): |
||||
kwargs['widget'] = widgets.AdminTextInputWidget |
||||
return db_field.formfield(**kwargs) |
||||
|
||||
# For FileFields and ImageFields add a link to the current file. |
||||
if isinstance(db_field, models.ImageField) or isinstance(db_field, models.FileField): |
||||
kwargs['widget'] = widgets.AdminFileWidget |
||||
return db_field.formfield(**kwargs) |
||||
|
||||
# For ForeignKey or ManyToManyFields, use a special widget. |
||||
if isinstance(db_field, (models.ForeignKey, models.ManyToManyField)): |
||||
if isinstance(db_field, models.ForeignKey) and db_field.name in self.raw_id_fields: |
||||
kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel) |
||||
elif isinstance(db_field, models.ForeignKey) and db_field.name in self.radio_fields: |
||||
kwargs['widget'] = widgets.AdminRadioSelect(attrs={ |
||||
'class': get_ul_class(self.radio_fields[db_field.name]), |
||||
}) |
||||
kwargs['empty_label'] = db_field.blank and _('None') or None |
||||
else: |
||||
if isinstance(db_field, models.ManyToManyField): |
||||
# If it uses an intermediary model, don't show field in admin. |
||||
if db_field.rel.through is not None: |
||||
return None |
||||
elif db_field.name in self.raw_id_fields: |
||||
kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel) |
||||
kwargs['help_text'] = '' |
||||
elif db_field.name in (list(self.filter_vertical) + list(self.filter_horizontal)): |
||||
kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical)) |
||||
# Wrap the widget's render() method with a method that adds |
||||
# extra HTML to the end of the rendered output. |
||||
formfield = db_field.formfield(**kwargs) |
||||
# Don't wrap raw_id fields. Their add function is in the popup window. |
||||
if not db_field.name in self.raw_id_fields: |
||||
# formfield can be None if it came from a OneToOneField with |
||||
# parent_link=True |
||||
if formfield is not None: |
||||
formfield.widget = widgets.RelatedFieldWidgetWrapper(formfield.widget, db_field.rel, self.admin_site) |
||||
return formfield |
||||
|
||||
# For any other type of field, just call its formfield() method. |
||||
return db_field.formfield(**kwargs) |
||||
|
||||
def _declared_fieldsets(self): |
||||
if self.fieldsets: |
||||
return self.fieldsets |
||||
elif self.fields: |
||||
return [(None, {'fields': self.fields})] |
||||
return None |
||||
declared_fieldsets = property(_declared_fieldsets) |
||||
|
||||
class ModelAdmin(BaseModelAdmin): |
||||
"Encapsulates all admin options and functionality for a given model." |
||||
__metaclass__ = forms.MediaDefiningClass |
||||
|
||||
list_display = ('__str__',) |
||||
list_display_links = () |
||||
list_filter = () |
||||
list_select_related = False |
||||
list_per_page = 100 |
||||
search_fields = () |
||||
date_hierarchy = None |
||||
save_as = False |
||||
save_on_top = False |
||||
ordering = None |
||||
inlines = [] |
||||
|
||||
# Custom templates (designed to be over-ridden in subclasses) |
||||
change_form_template = None |
||||
change_list_template = None |
||||
delete_confirmation_template = None |
||||
object_history_template = None |
||||
|
||||
def __init__(self, model, admin_site): |
||||
self.model = model |
||||
self.opts = model._meta |
||||
self.admin_site = admin_site |
||||
self.inline_instances = [] |
||||
for inline_class in self.inlines: |
||||
inline_instance = inline_class(self.model, self.admin_site) |
||||
self.inline_instances.append(inline_instance) |
||||
super(ModelAdmin, self).__init__() |
||||
|
||||
def __call__(self, request, url): |
||||
# Delegate to the appropriate method, based on the URL. |
||||
if url is None: |
||||
return self.changelist_view(request) |
||||
elif url == "add": |
||||
return self.add_view(request) |
||||
elif url.endswith('/history'): |
||||
return self.history_view(request, unquote(url[:-8])) |
||||
elif url.endswith('/delete'): |
||||
return self.delete_view(request, unquote(url[:-7])) |
||||
else: |
||||
return self.change_view(request, unquote(url)) |
||||
|
||||
def _media(self): |
||||
from django.conf import settings |
||||
|
||||
js = ['js/core.js', 'js/admin/RelatedObjectLookups.js'] |
||||
if self.prepopulated_fields: |
||||
js.append('js/urlify.js') |
||||
if self.opts.get_ordered_objects(): |
||||
js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js']) |
||||
|
||||
return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js]) |
||||
media = property(_media) |
||||
|
||||
def has_add_permission(self, request): |
||||
"Returns True if the given request has permission to add an object." |
||||
opts = self.opts |
||||
return request.user.has_perm(opts.app_label + '.' + opts.get_add_permission()) |
||||
|
||||
def has_change_permission(self, request, obj=None): |
||||
""" |
||||
Returns True if the given request has permission to change the given |
||||
Django model instance. |
||||
|
||||
If `obj` is None, this should return True if the given request has |
||||
permission to change *any* object of the given type. |
||||
""" |
||||
opts = self.opts |
||||
return request.user.has_perm(opts.app_label + '.' + opts.get_change_permission()) |
||||
|
||||
def has_delete_permission(self, request, obj=None): |
||||
""" |
||||
Returns True if the given request has permission to change the given |
||||
Django model instance. |
||||
|
||||
If `obj` is None, this should return True if the given request has |
||||
permission to delete *any* object of the given type. |
||||
""" |
||||
opts = self.opts |
||||
return request.user.has_perm(opts.app_label + '.' + opts.get_delete_permission()) |
||||
|
||||
def queryset(self, request): |
||||
""" |
||||
Returns a QuerySet of all model instances that can be edited by the |
||||
admin site. This is used by changelist_view. |
||||
""" |
||||
qs = self.model._default_manager.get_query_set() |
||||
# TODO: this should be handled by some parameter to the ChangeList. |
||||
ordering = self.ordering or () # otherwise we might try to *None, which is bad ;) |
||||
if ordering: |
||||
qs = qs.order_by(*ordering) |
||||
return qs |
||||
|
||||
def get_fieldsets(self, request, obj=None): |
||||
"Hook for specifying fieldsets for the add form." |
||||
if self.declared_fieldsets: |
||||
return self.declared_fieldsets |
||||
form = self.get_form(request, obj) |
||||
return [(None, {'fields': form.base_fields.keys()})] |
||||
|
||||
def get_form(self, request, obj=None, **kwargs): |
||||
""" |
||||
Returns a Form class for use in the admin add view. This is used by |
||||
add_view and change_view. |
||||
""" |
||||
if self.declared_fieldsets: |
||||
fields = flatten_fieldsets(self.declared_fieldsets) |
||||
else: |
||||
fields = None |
||||
if self.exclude is None: |
||||
exclude = [] |
||||
else: |
||||
exclude = self.exclude |
||||
defaults = { |
||||
"form": self.form, |
||||
"fields": fields, |
||||
"exclude": exclude + kwargs.get("exclude", []), |
||||
"formfield_callback": self.formfield_for_dbfield, |
||||
} |
||||
defaults.update(kwargs) |
||||
return modelform_factory(self.model, **defaults) |
||||
|
||||
def get_formsets(self, request, obj=None): |
||||
for inline in self.inline_instances: |
||||
yield inline.get_formset(request, obj) |
||||
|
||||
def log_addition(self, request, object): |
||||
""" |
||||
Log that an object has been successfully added. |
||||
|
||||
The default implementation creates an admin LogEntry object. |
||||
""" |
||||
from django.contrib.admin.models import LogEntry, ADDITION |
||||
LogEntry.objects.log_action( |
||||
user_id = request.user.pk, |
||||
content_type_id = ContentType.objects.get_for_model(object).pk, |
||||
object_id = object.pk, |
||||
object_repr = force_unicode(object), |
||||
action_flag = ADDITION |
||||
) |
||||
|
||||
def log_change(self, request, object, message): |
||||
""" |
||||
Log that an object has been successfully changed. |
||||
|
||||
The default implementation creates an admin LogEntry object. |
||||
""" |
||||
from django.contrib.admin.models import LogEntry, CHANGE |
||||
LogEntry.objects.log_action( |
||||
user_id = request.user.pk, |
||||
content_type_id = ContentType.objects.get_for_model(object).pk, |
||||
object_id = object.pk, |
||||
object_repr = force_unicode(object), |
||||
action_flag = CHANGE, |
||||
change_message = message |
||||
) |
||||
|
||||
def log_deletion(self, request, object, object_repr): |
||||
""" |
||||
Log that an object has been successfully deleted. Note that since the |
||||
object is deleted, it might no longer be safe to call *any* methods |
||||
on the object, hence this method getting object_repr. |
||||
|
||||
The default implementation creates an admin LogEntry object. |
||||
""" |
||||
from django.contrib.admin.models import LogEntry, DELETION |
||||
LogEntry.objects.log_action( |
||||
user_id = request.user.id, |
||||
content_type_id = ContentType.objects.get_for_model(self.model).pk, |
||||
object_id = object.pk, |
||||
object_repr = object_repr, |
||||
action_flag = DELETION |
||||
) |
||||
|
||||
|
||||
def construct_change_message(self, request, form, formsets): |
||||
""" |
||||
Construct a change message from a changed object. |
||||
""" |
||||
change_message = [] |
||||
if form.changed_data: |
||||
change_message.append(_('Changed %s.') % get_text_list(form.changed_data, _('and'))) |
||||
|
||||
if formsets: |
||||
for formset in formsets: |
||||
for added_object in formset.new_objects: |
||||
change_message.append(_('Added %(name)s "%(object)s".') |
||||
% {'name': added_object._meta.verbose_name, |
||||
'object': added_object}) |
||||
for changed_object, changed_fields in formset.changed_objects: |
||||
change_message.append(_('Changed %(list)s for %(name)s "%(object)s".') |
||||
% {'list': get_text_list(changed_fields, _('and')), |
||||
'name': changed_object._meta.verbose_name, |
||||
'object': changed_object}) |
||||
for deleted_object in formset.deleted_objects: |
||||
change_message.append(_('Deleted %(name)s "%(object)s".') |
||||
% {'name': deleted_object._meta.verbose_name, |
||||
'object': deleted_object}) |
||||
change_message = ' '.join(change_message) |
||||
return change_message or _('No fields changed.') |
||||
|
||||
def message_user(self, request, message): |
||||
""" |
||||
Send a message to the user. The default implementation |
||||
posts a message using the auth Message object. |
||||
""" |
||||
request.user.message_set.create(message=message) |
||||
|
||||
def save_form(self, request, form, change): |
||||
""" |
||||
Given a ModelForm return an unsaved instance. ``change`` is True if |
||||
the object is being changed, and False if it's being added. |
||||
""" |
||||
return form.save(commit=False) |
||||
|
||||
def save_model(self, request, obj, form, change): |
||||
""" |
||||
Given a model instance save it to the database. |
||||
""" |
||||
obj.save() |
||||
|
||||
def save_formset(self, request, form, formset, change): |
||||
""" |
||||
Given an inline formset save it to the database. |
||||
""" |
||||
formset.save() |
||||
|
||||
def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None): |
||||
opts = self.model._meta |
||||
app_label = opts.app_label |
||||
ordered_objects = opts.get_ordered_objects() |
||||
context.update({ |
||||
'add': add, |
||||
'change': change, |
||||
'has_add_permission': self.has_add_permission(request), |
||||
'has_change_permission': self.has_change_permission(request, obj), |
||||
'has_delete_permission': self.has_delete_permission(request, obj), |
||||
'has_file_field': True, # FIXME - this should check if form or formsets have a FileField, |
||||
'has_absolute_url': hasattr(self.model, 'get_absolute_url'), |
||||
'ordered_objects': ordered_objects, |
||||
'form_url': mark_safe(form_url), |
||||
'opts': opts, |
||||
'content_type_id': ContentType.objects.get_for_model(self.model).id, |
||||
'save_as': self.save_as, |
||||
'save_on_top': self.save_on_top, |
||||
'root_path': self.admin_site.root_path, |
||||
}) |
||||
return render_to_response(self.change_form_template or [ |
||||
"admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()), |
||||
"admin/%s/change_form.html" % app_label, |
||||
"admin/change_form.html" |
||||
], context, context_instance=template.RequestContext(request)) |
||||
|
||||
def response_add(self, request, obj, post_url_continue='../%s/'): |
||||
""" |
||||
Determines the HttpResponse for the add_view stage. |
||||
""" |
||||
opts = obj._meta |
||||
pk_value = obj._get_pk_val() |
||||
|
||||
msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj)} |
||||
# Here, we distinguish between different save types by checking for |
||||
# the presence of keys in request.POST. |
||||
if request.POST.has_key("_continue"): |
||||
self.message_user(request, msg + ' ' + _("You may edit it again below.")) |
||||
if request.POST.has_key("_popup"): |
||||
post_url_continue += "?_popup=1" |
||||
return HttpResponseRedirect(post_url_continue % pk_value) |
||||
|
||||
if request.POST.has_key("_popup"): |
||||
return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script>' % \ |
||||
# escape() calls force_unicode. |
||||
(escape(pk_value), escape(obj))) |
||||
elif request.POST.has_key("_addanother"): |
||||
self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name))) |
||||
return HttpResponseRedirect(request.path) |
||||
else: |
||||
self.message_user(request, msg) |
||||
|
||||
# Figure out where to redirect. If the user has change permission, |
||||
# redirect to the change-list page for this object. Otherwise, |
||||
# redirect to the admin index. |
||||
if self.has_change_permission(request, None): |
||||
post_url = '../' |
||||
else: |
||||
post_url = '../../../' |
||||
return HttpResponseRedirect(post_url) |
||||
|
||||
def response_change(self, request, obj): |
||||
""" |
||||
Determines the HttpResponse for the change_view stage. |
||||
""" |
||||
opts = obj._meta |
||||
pk_value = obj._get_pk_val() |
||||
|
||||
msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj)} |
||||
if request.POST.has_key("_continue"): |
||||
self.message_user(request, msg + ' ' + _("You may edit it again below.")) |
||||
if request.REQUEST.has_key('_popup'): |
||||
return HttpResponseRedirect(request.path + "?_popup=1") |
||||
else: |
||||
return HttpResponseRedirect(request.path) |
||||
elif request.POST.has_key("_saveasnew"): |
||||
msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': force_unicode(opts.verbose_name), 'obj': obj} |
||||
self.message_user(request, msg) |
||||
return HttpResponseRedirect("../%s/" % pk_value) |
||||
elif request.POST.has_key("_addanother"): |
||||
self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name))) |
||||
return HttpResponseRedirect("../add/") |
||||
else: |
||||
self.message_user(request, msg) |
||||
return HttpResponseRedirect("../") |
||||
|
||||
def add_view(self, request, form_url='', extra_context=None): |
||||
"The 'add' admin view for this model." |
||||
model = self.model |
||||
opts = model._meta |
||||
app_label = opts.app_label |
||||
|
||||
if not self.has_add_permission(request): |
||||
raise PermissionDenied |
||||
|
||||
if self.has_change_permission(request, None): |
||||
# redirect to list view |
||||
post_url = '../' |
||||
else: |
||||
# Object list will give 'Permission Denied', so go back to admin home |
||||
post_url = '../../../' |
||||
|
||||
ModelForm = self.get_form(request) |
||||
formsets = [] |
||||
if request.method == 'POST': |
||||
form = ModelForm(request.POST, request.FILES) |
||||
if form.is_valid(): |
||||
form_validated = True |
||||
new_object = self.save_form(request, form, change=False) |
||||
else: |
||||
form_validated = False |
||||
new_object = self.model() |
||||
for FormSet in self.get_formsets(request): |
||||
formset = FormSet(data=request.POST, files=request.FILES, |
||||
instance=new_object, |
||||
save_as_new=request.POST.has_key("_saveasnew")) |
||||
formsets.append(formset) |
||||
if all_valid(formsets) and form_validated: |
||||
self.save_model(request, new_object, form, change=False) |
||||
form.save_m2m() |
||||
for formset in formsets: |
||||
self.save_formset(request, form, formset, change=False) |
||||
|
||||
self.log_addition(request, new_object) |
||||
return self.response_add(request, new_object) |
||||
else: |
||||
# Prepare the dict of initial data from the request. |
||||
# We have to special-case M2Ms as a list of comma-separated PKs. |
||||
initial = dict(request.GET.items()) |
||||
for k in initial: |
||||
try: |
||||
f = opts.get_field(k) |
||||
except models.FieldDoesNotExist: |
||||
continue |
||||
if isinstance(f, models.ManyToManyField): |
||||
initial[k] = initial[k].split(",") |
||||
form = ModelForm(initial=initial) |
||||
for FormSet in self.get_formsets(request): |
||||
formset = FormSet(instance=self.model()) |
||||
formsets.append(formset) |
||||
|
||||
adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)), self.prepopulated_fields) |
||||
media = self.media + adminForm.media |
||||
|
||||
inline_admin_formsets = [] |
||||
for inline, formset in zip(self.inline_instances, formsets): |
||||
fieldsets = list(inline.get_fieldsets(request)) |
||||
inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, fieldsets) |
||||
inline_admin_formsets.append(inline_admin_formset) |
||||
media = media + inline_admin_formset.media |
||||
|
||||
context = { |
||||
'title': _('Add %s') % force_unicode(opts.verbose_name), |
||||
'adminform': adminForm, |
||||
'is_popup': request.REQUEST.has_key('_popup'), |
||||
'show_delete': False, |
||||
'media': mark_safe(media), |
||||
'inline_admin_formsets': inline_admin_formsets, |
||||
'errors': helpers.AdminErrorList(form, formsets), |
||||
'root_path': self.admin_site.root_path, |
||||
'app_label': app_label, |
||||
} |
||||
context.update(extra_context or {}) |
||||
return self.render_change_form(request, context, add=True) |
||||
add_view = transaction.commit_on_success(add_view) |
||||
|
||||
def change_view(self, request, object_id, extra_context=None): |
||||
"The 'change' admin view for this model." |
||||
model = self.model |
||||
opts = model._meta |
||||
app_label = opts.app_label |
||||
|
||||
try: |
||||
obj = model._default_manager.get(pk=object_id) |
||||
except model.DoesNotExist: |
||||
# Don't raise Http404 just yet, because we haven't checked |
||||
# permissions yet. We don't want an unauthenticated user to be able |
||||
# to determine whether a given object exists. |
||||
obj = None |
||||
|
||||
if not self.has_change_permission(request, obj): |
||||
raise PermissionDenied |
||||
|
||||
if obj is None: |
||||
raise Http404('%s object with primary key %r does not exist.' % (force_unicode(opts.verbose_name), escape(object_id))) |
||||
|
||||
if request.POST and request.POST.has_key("_saveasnew"): |
||||
return self.add_view(request, form_url='../../add/') |
||||
|
||||
ModelForm = self.get_form(request, obj) |
||||
formsets = [] |
||||
if request.method == 'POST': |
||||
form = ModelForm(request.POST, request.FILES, instance=obj) |
||||
if form.is_valid(): |
||||
form_validated = True |
||||
new_object = self.save_form(request, form, change=True) |
||||
else: |
||||
form_validated = False |
||||
new_object = obj |
||||
for FormSet in self.get_formsets(request, new_object): |
||||
formset = FormSet(request.POST, request.FILES, |
||||
instance=new_object) |
||||
formsets.append(formset) |
||||
|
||||
if all_valid(formsets) and form_validated: |
||||
self.save_model(request, new_object, form, change=True) |
||||
form.save_m2m() |
||||
for formset in formsets: |
||||
self.save_formset(request, form, formset, change=True) |
||||
|
||||
change_message = self.construct_change_message(request, form, formsets) |
||||
self.log_change(request, new_object, change_message) |
||||
return self.response_change(request, new_object) |
||||
else: |
||||
form = ModelForm(instance=obj) |
||||
for FormSet in self.get_formsets(request, obj): |
||||
formset = FormSet(instance=obj) |
||||
formsets.append(formset) |
||||
|
||||
adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj), self.prepopulated_fields) |
||||
media = self.media + adminForm.media |
||||
|
||||
inline_admin_formsets = [] |
||||
for inline, formset in zip(self.inline_instances, formsets): |
||||
fieldsets = list(inline.get_fieldsets(request, obj)) |
||||
inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, fieldsets) |
||||
inline_admin_formsets.append(inline_admin_formset) |
||||
media = media + inline_admin_formset.media |
||||
|
||||
context = { |
||||
'title': _('Change %s') % force_unicode(opts.verbose_name), |
||||
'adminform': adminForm, |
||||
'object_id': object_id, |
||||
'original': obj, |
||||
'is_popup': request.REQUEST.has_key('_popup'), |
||||
'media': mark_safe(media), |
||||
'inline_admin_formsets': inline_admin_formsets, |
||||
'errors': helpers.AdminErrorList(form, formsets), |
||||
'root_path': self.admin_site.root_path, |
||||
'app_label': app_label, |
||||
} |
||||
context.update(extra_context or {}) |
||||
return self.render_change_form(request, context, change=True, obj=obj) |
||||
change_view = transaction.commit_on_success(change_view) |
||||
|
||||
def changelist_view(self, request, extra_context=None): |
||||
"The 'change list' admin view for this model." |
||||
from django.contrib.admin.views.main import ChangeList, ERROR_FLAG |
||||
opts = self.model._meta |
||||
app_label = opts.app_label |
||||
if not self.has_change_permission(request, None): |
||||
raise PermissionDenied |
||||
try: |
||||
cl = ChangeList(request, self.model, self.list_display, self.list_display_links, self.list_filter, |
||||
self.date_hierarchy, self.search_fields, self.list_select_related, self.list_per_page, self) |
||||
except IncorrectLookupParameters: |
||||
# Wacky lookup parameters were given, so redirect to the main |
||||
# changelist page, without parameters, and pass an 'invalid=1' |
||||
# parameter via the query string. If wacky parameters were given and |
||||
# the 'invalid=1' parameter was already in the query string, something |
||||
# is screwed up with the database, so display an error page. |
||||
if ERROR_FLAG in request.GET.keys(): |
||||
return render_to_response('admin/invalid_setup.html', {'title': _('Database error')}) |
||||
return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1') |
||||
|
||||
context = { |
||||
'title': cl.title, |
||||
'is_popup': cl.is_popup, |
||||
'cl': cl, |
||||
'has_add_permission': self.has_add_permission(request), |
||||
'root_path': self.admin_site.root_path, |
||||
'app_label': app_label, |
||||
} |
||||
context.update(extra_context or {}) |
||||
return render_to_response(self.change_list_template or [ |
||||
'admin/%s/%s/change_list.html' % (app_label, opts.object_name.lower()), |
||||
'admin/%s/change_list.html' % app_label, |
||||
'admin/change_list.html' |
||||
], context, context_instance=template.RequestContext(request)) |
||||
|
||||
def delete_view(self, request, object_id, extra_context=None): |
||||
"The 'delete' admin view for this model." |
||||
opts = self.model._meta |
||||
app_label = opts.app_label |
||||
|
||||
try: |
||||
obj = self.model._default_manager.get(pk=object_id) |
||||
except self.model.DoesNotExist: |
||||
# Don't raise Http404 just yet, because we haven't checked |
||||
# permissions yet. We don't want an unauthenticated user to be able |
||||
# to determine whether a given object exists. |
||||
obj = None |
||||
|
||||
if not self.has_delete_permission(request, obj): |
||||
raise PermissionDenied |
||||
|
||||
if obj is None: |
||||
raise Http404('%s object with primary key %r does not exist.' % (force_unicode(opts.verbose_name), escape(object_id))) |
||||
|
||||
# Populate deleted_objects, a data structure of all related objects that |
||||
# will also be deleted. |
||||
deleted_objects = [mark_safe(u'%s: <a href="../../%s/">%s</a>' % (escape(force_unicode(capfirst(opts.verbose_name))), quote(object_id), escape(obj))), []] |
||||
perms_needed = set() |
||||
get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1, self.admin_site) |
||||
|
||||
if request.POST: # The user has already confirmed the deletion. |
||||
if perms_needed: |
||||
raise PermissionDenied |
||||
obj_display = str(obj) |
||||
obj.delete() |
||||
|
||||
self.log_deletion(request, obj, obj_display) |
||||
self.message_user(request, _('The %(name)s "%(obj)s" was deleted successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj_display)}) |
||||
|
||||
if not self.has_change_permission(request, None): |
||||
return HttpResponseRedirect("../../../../") |
||||
return HttpResponseRedirect("../../") |
||||
|
||||
context = { |
||||
"title": _("Are you sure?"), |
||||
"object_name": force_unicode(opts.verbose_name), |
||||
"object": obj, |
||||
"deleted_objects": deleted_objects, |
||||
"perms_lacking": perms_needed, |
||||
"opts": opts, |
||||
"root_path": self.admin_site.root_path, |
||||
"app_label": app_label, |
||||
} |
||||
context.update(extra_context or {}) |
||||
return render_to_response(self.delete_confirmation_template or [ |
||||
"admin/%s/%s/delete_confirmation.html" % (app_label, opts.object_name.lower()), |
||||
"admin/%s/delete_confirmation.html" % app_label, |
||||
"admin/delete_confirmation.html" |
||||
], context, context_instance=template.RequestContext(request)) |
||||
|
||||
def history_view(self, request, object_id, extra_context=None): |
||||
"The 'history' admin view for this model." |
||||
from django.contrib.admin.models import LogEntry |
||||
model = self.model |
||||
opts = model._meta |
||||
app_label = opts.app_label |
||||
action_list = LogEntry.objects.filter( |
||||
object_id = object_id, |
||||
content_type__id__exact = ContentType.objects.get_for_model(model).id |
||||
).select_related().order_by('action_time') |
||||
# If no history was found, see whether this object even exists. |
||||
obj = get_object_or_404(model, pk=object_id) |
||||
context = { |
||||
'title': _('Change history: %s') % force_unicode(obj), |
||||
'action_list': action_list, |
||||
'module_name': capfirst(force_unicode(opts.verbose_name_plural)), |
||||
'object': obj, |
||||
'root_path': self.admin_site.root_path, |
||||
'app_label': app_label, |
||||
} |
||||
context.update(extra_context or {}) |
||||
return render_to_response(self.object_history_template or [ |
||||
"admin/%s/%s/object_history.html" % (opts.app_label, opts.object_name.lower()), |
||||
"admin/%s/object_history.html" % opts.app_label, |
||||
"admin/object_history.html" |
||||
], context, context_instance=template.RequestContext(request)) |
||||
|
||||
class InlineModelAdmin(BaseModelAdmin): |
||||
""" |
||||
Options for inline editing of ``model`` instances. |
||||
|
||||
Provide ``name`` to specify the attribute name of the ``ForeignKey`` from |
||||
``model`` to its parent. This is required if ``model`` has more than one |
||||
``ForeignKey`` to its parent. |
||||
""" |
||||
model = None |
||||
fk_name = None |
||||
formset = BaseInlineFormSet |
||||
extra = 3 |
||||
max_num = 0 |
||||
template = None |
||||
verbose_name = None |
||||
verbose_name_plural = None |
||||
|
||||
def __init__(self, parent_model, admin_site): |
||||
self.admin_site = admin_site |
||||
self.parent_model = parent_model |
||||
self.opts = self.model._meta |
||||
super(InlineModelAdmin, self).__init__() |
||||
if self.verbose_name is None: |
||||
self.verbose_name = self.model._meta.verbose_name |
||||
if self.verbose_name_plural is None: |
||||
self.verbose_name_plural = self.model._meta.verbose_name_plural |
||||
|
||||
def _media(self): |
||||
from django.conf import settings |
||||
js = [] |
||||
if self.prepopulated_fields: |
||||
js.append('js/urlify.js') |
||||
if self.filter_vertical or self.filter_horizontal: |
||||
js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js']) |
||||
return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js]) |
||||
media = property(_media) |
||||
|
||||
def get_formset(self, request, obj=None, **kwargs): |
||||
"""Returns a BaseInlineFormSet class for use in admin add/change views.""" |
||||
if self.declared_fieldsets: |
||||
fields = flatten_fieldsets(self.declared_fieldsets) |
||||
else: |
||||
fields = None |
||||
if self.exclude is None: |
||||
exclude = [] |
||||
else: |
||||
exclude = self.exclude |
||||
defaults = { |
||||
"form": self.form, |
||||
"formset": self.formset, |
||||
"fk_name": self.fk_name, |
||||
"fields": fields, |
||||
"exclude": exclude + kwargs.get("exclude", []), |
||||
"formfield_callback": self.formfield_for_dbfield, |
||||
"extra": self.extra, |
||||
"max_num": self.max_num, |
||||
} |
||||
defaults.update(kwargs) |
||||
return inlineformset_factory(self.parent_model, self.model, **defaults) |
||||
|
||||
def get_fieldsets(self, request, obj=None): |
||||
if self.declared_fieldsets: |
||||
return self.declared_fieldsets |
||||
form = self.get_formset(request).form |
||||
return [(None, {'fields': form.base_fields.keys()})] |
||||
|
||||
class StackedInline(InlineModelAdmin): |
||||
template = 'admin/edit_inline/stacked.html' |
||||
|
||||
class TabularInline(InlineModelAdmin): |
||||
template = 'admin/edit_inline/tabular.html' |
||||
@ -0,0 +1,384 @@
@@ -0,0 +1,384 @@
|
||||
import base64 |
||||
import re |
||||
from django import http, template |
||||
from django.contrib.admin import ModelAdmin |
||||
from django.contrib.auth import authenticate, login |
||||
from django.db.models.base import ModelBase |
||||
from django.core.exceptions import ImproperlyConfigured |
||||
from django.shortcuts import render_to_response |
||||
from django.utils.safestring import mark_safe |
||||
from django.utils.text import capfirst |
||||
from django.utils.translation import ugettext_lazy, ugettext as _ |
||||
from django.views.decorators.cache import never_cache |
||||
from django.conf import settings |
||||
from django.utils.hashcompat import md5_constructor |
||||
|
||||
ERROR_MESSAGE = ugettext_lazy("Please enter a correct username and password. Note that both fields are case-sensitive.") |
||||
LOGIN_FORM_KEY = 'this_is_the_login_form' |
||||
|
||||
class AlreadyRegistered(Exception): |
||||
pass |
||||
|
||||
class NotRegistered(Exception): |
||||
pass |
||||
|
||||
class AdminSite(object): |
||||
""" |
||||
An AdminSite object encapsulates an instance of the Django admin application, ready |
||||
to be hooked in to your URLConf. Models are registered with the AdminSite using the |
||||
register() method, and the root() method can then be used as a Django view function |
||||
that presents a full admin interface for the collection of registered models. |
||||
""" |
||||
|
||||
index_template = None |
||||
login_template = None |
||||
app_index_template = None |
||||
|
||||
def __init__(self): |
||||
self._registry = {} # model_class class -> admin_class instance |
||||
|
||||
def register(self, model_or_iterable, admin_class=None, **options): |
||||
""" |
||||
Registers the given model(s) with the given admin class. |
||||
|
||||
The model(s) should be Model classes, not instances. |
||||
|
||||
If an admin class isn't given, it will use ModelAdmin (the default |
||||
admin options). If keyword arguments are given -- e.g., list_display -- |
||||
they'll be applied as options to the admin class. |
||||
|
||||
If a model is already registered, this will raise AlreadyRegistered. |
||||
""" |
||||
# Don't import the humongous validation code unless required |
||||
if admin_class and settings.DEBUG: |
||||
from django.contrib.admin.validation import validate |
||||
else: |
||||
validate = lambda model, adminclass: None |
||||
|
||||
if not admin_class: |
||||
admin_class = ModelAdmin |
||||
if isinstance(model_or_iterable, ModelBase): |
||||
model_or_iterable = [model_or_iterable] |
||||
for model in model_or_iterable: |
||||
if model in self._registry: |
||||
raise AlreadyRegistered('The model %s is already registered' % model.__name__) |
||||
|
||||
# If we got **options then dynamically construct a subclass of |
||||
# admin_class with those **options. |
||||
if options: |
||||
# For reasons I don't quite understand, without a __module__ |
||||
# the created class appears to "live" in the wrong place, |
||||
# which causes issues later on. |
||||
options['__module__'] = __name__ |
||||
admin_class = type("%sAdmin" % model.__name__, (admin_class,), options) |
||||
|
||||
# Validate (which might be a no-op) |
||||
validate(admin_class, model) |
||||
|
||||
# Instantiate the admin class to save in the registry |
||||
self._registry[model] = admin_class(model, self) |
||||
|
||||
def unregister(self, model_or_iterable): |
||||
""" |
||||
Unregisters the given model(s). |
||||
|
||||
If a model isn't already registered, this will raise NotRegistered. |
||||
""" |
||||
if isinstance(model_or_iterable, ModelBase): |
||||
model_or_iterable = [model_or_iterable] |
||||
for model in model_or_iterable: |
||||
if model not in self._registry: |
||||
raise NotRegistered('The model %s is not registered' % model.__name__) |
||||
del self._registry[model] |
||||
|
||||
def has_permission(self, request): |
||||
""" |
||||
Returns True if the given HttpRequest has permission to view |
||||
*at least one* page in the admin site. |
||||
""" |
||||
return request.user.is_authenticated() and request.user.is_staff |
||||
|
||||
def check_dependencies(self): |
||||
""" |
||||
Check that all things needed to run the admin have been correctly installed. |
||||
|
||||
The default implementation checks that LogEntry, ContentType and the |
||||
auth context processor are installed. |
||||
""" |
||||
from django.conf import settings |
||||
from django.contrib.admin.models import LogEntry |
||||
from django.contrib.contenttypes.models import ContentType |
||||
|
||||
if not LogEntry._meta.installed: |
||||
raise ImproperlyConfigured("Put 'django.contrib.admin' in your INSTALLED_APPS setting in order to use the admin application.") |
||||
if not ContentType._meta.installed: |
||||
raise ImproperlyConfigured("Put 'django.contrib.contenttypes' in your INSTALLED_APPS setting in order to use the admin application.") |
||||
if 'django.core.context_processors.auth' not in settings.TEMPLATE_CONTEXT_PROCESSORS: |
||||
raise ImproperlyConfigured("Put 'django.core.context_processors.auth' in your TEMPLATE_CONTEXT_PROCESSORS setting in order to use the admin application.") |
||||
|
||||
def root(self, request, url): |
||||
""" |
||||
Handles main URL routing for the admin app. |
||||
|
||||
`url` is the remainder of the URL -- e.g. 'comments/comment/'. |
||||
""" |
||||
if request.method == 'GET' and not request.path.endswith('/'): |
||||
return http.HttpResponseRedirect(request.path + '/') |
||||
|
||||
if settings.DEBUG: |
||||
self.check_dependencies() |
||||
|
||||
# Figure out the admin base URL path and stash it for later use |
||||
self.root_path = re.sub(re.escape(url) + '$', '', request.path) |
||||
|
||||
url = url.rstrip('/') # Trim trailing slash, if it exists. |
||||
|
||||
# The 'logout' view doesn't require that the person is logged in. |
||||
if url == 'logout': |
||||
return self.logout(request) |
||||
|
||||
# Check permission to continue or display login form. |
||||
if not self.has_permission(request): |
||||
return self.login(request) |
||||
|
||||
if url == '': |
||||
return self.index(request) |
||||
elif url == 'password_change': |
||||
return self.password_change(request) |
||||
elif url == 'password_change/done': |
||||
return self.password_change_done(request) |
||||
elif url == 'jsi18n': |
||||
return self.i18n_javascript(request) |
||||
# urls starting with 'r/' are for the "show in web" links |
||||
elif url.startswith('r/'): |
||||
from django.views.defaults import shortcut |
||||
return shortcut(request, *url.split('/')[1:]) |
||||
else: |
||||
if '/' in url: |
||||
return self.model_page(request, *url.split('/', 2)) |
||||
else: |
||||
return self.app_index(request, url) |
||||
|
||||
raise http.Http404('The requested admin page does not exist.') |
||||
|
||||
def model_page(self, request, app_label, model_name, rest_of_url=None): |
||||
""" |
||||
Handles the model-specific functionality of the admin site, delegating |
||||
to the appropriate ModelAdmin class. |
||||
""" |
||||
from django.db import models |
||||
model = models.get_model(app_label, model_name) |
||||
if model is None: |
||||
raise http.Http404("App %r, model %r, not found." % (app_label, model_name)) |
||||
try: |
||||
admin_obj = self._registry[model] |
||||
except KeyError: |
||||
raise http.Http404("This model exists but has not been registered with the admin site.") |
||||
return admin_obj(request, rest_of_url) |
||||
model_page = never_cache(model_page) |
||||
|
||||
def password_change(self, request): |
||||
""" |
||||
Handles the "change password" task -- both form display and validation. |
||||
""" |
||||
from django.contrib.auth.views import password_change |
||||
return password_change(request, |
||||
post_change_redirect='%spassword_change/done/' % self.root_path) |
||||
|
||||
def password_change_done(self, request): |
||||
""" |
||||
Displays the "success" page after a password change. |
||||
""" |
||||
from django.contrib.auth.views import password_change_done |
||||
return password_change_done(request) |
||||
|
||||
def i18n_javascript(self, request): |
||||
""" |
||||
Displays the i18n JavaScript that the Django admin requires. |
||||
|
||||
This takes into account the USE_I18N setting. If it's set to False, the |
||||
generated JavaScript will be leaner and faster. |
||||
""" |
||||
if settings.USE_I18N: |
||||
from django.views.i18n import javascript_catalog |
||||
else: |
||||
from django.views.i18n import null_javascript_catalog as javascript_catalog |
||||
return javascript_catalog(request, packages='django.conf') |
||||
|
||||
def logout(self, request): |
||||
""" |
||||
Logs out the user for the given HttpRequest. |
||||
|
||||
This should *not* assume the user is already logged in. |
||||
""" |
||||
from django.contrib.auth.views import logout |
||||
return logout(request) |
||||
logout = never_cache(logout) |
||||
|
||||
def login(self, request): |
||||
""" |
||||
Displays the login form for the given HttpRequest. |
||||
""" |
||||
from django.contrib.auth.models import User |
||||
|
||||
# If this isn't already the login page, display it. |
||||
if not request.POST.has_key(LOGIN_FORM_KEY): |
||||
if request.POST: |
||||
message = _("Please log in again, because your session has expired.") |
||||
else: |
||||
message = "" |
||||
return self.display_login_form(request, message) |
||||
|
||||
# Check that the user accepts cookies. |
||||
if not request.session.test_cookie_worked(): |
||||
message = _("Looks like your browser isn't configured to accept cookies. Please enable cookies, reload this page, and try again.") |
||||
return self.display_login_form(request, message) |
||||
else: |
||||
request.session.delete_test_cookie() |
||||
|
||||
# Check the password. |
||||
username = request.POST.get('username', None) |
||||
password = request.POST.get('password', None) |
||||
user = authenticate(username=username, password=password) |
||||
if user is None: |
||||
message = ERROR_MESSAGE |
||||
if u'@' in username: |
||||
# Mistakenly entered e-mail address instead of username? Look it up. |
||||
try: |
||||
user = User.objects.get(email=username) |
||||
except (User.DoesNotExist, User.MultipleObjectsReturned): |
||||
message = _("Usernames cannot contain the '@' character.") |
||||
else: |
||||
if user.check_password(password): |
||||
message = _("Your e-mail address is not your username." |
||||
" Try '%s' instead.") % user.username |
||||
else: |
||||
message = _("Usernames cannot contain the '@' character.") |
||||
return self.display_login_form(request, message) |
||||
|
||||
# The user data is correct; log in the user in and continue. |
||||
else: |
||||
if user.is_active and user.is_staff: |
||||
login(request, user) |
||||
return http.HttpResponseRedirect(request.get_full_path()) |
||||
else: |
||||
return self.display_login_form(request, ERROR_MESSAGE) |
||||
login = never_cache(login) |
||||
|
||||
def index(self, request, extra_context=None): |
||||
""" |
||||
Displays the main admin index page, which lists all of the installed |
||||
apps that have been registered in this site. |
||||
""" |
||||
app_dict = {} |
||||
user = request.user |
||||
for model, model_admin in self._registry.items(): |
||||
app_label = model._meta.app_label |
||||
has_module_perms = user.has_module_perms(app_label) |
||||
|
||||
if has_module_perms: |
||||
perms = { |
||||
'add': model_admin.has_add_permission(request), |
||||
'change': model_admin.has_change_permission(request), |
||||
'delete': model_admin.has_delete_permission(request), |
||||
} |
||||
|
||||
# Check whether user has any perm for this module. |
||||
# If so, add the module to the model_list. |
||||
if True in perms.values(): |
||||
model_dict = { |
||||
'name': capfirst(model._meta.verbose_name_plural), |
||||
'admin_url': mark_safe('%s/%s/' % (app_label, model.__name__.lower())), |
||||
'perms': perms, |
||||
} |
||||
if app_label in app_dict: |
||||
app_dict[app_label]['models'].append(model_dict) |
||||
else: |
||||
app_dict[app_label] = { |
||||
'name': app_label.title(), |
||||
'app_url': app_label, |
||||
'has_module_perms': has_module_perms, |
||||
'models': [model_dict], |
||||
} |
||||
|
||||
# Sort the apps alphabetically. |
||||
app_list = app_dict.values() |
||||
app_list.sort(lambda x, y: cmp(x['name'], y['name'])) |
||||
|
||||
# Sort the models alphabetically within each app. |
||||
for app in app_list: |
||||
app['models'].sort(lambda x, y: cmp(x['name'], y['name'])) |
||||
|
||||
context = { |
||||
'title': _('Site administration'), |
||||
'app_list': app_list, |
||||
'root_path': self.root_path, |
||||
} |
||||
context.update(extra_context or {}) |
||||
return render_to_response(self.index_template or 'admin/index.html', context, |
||||
context_instance=template.RequestContext(request) |
||||
) |
||||
index = never_cache(index) |
||||
|
||||
def display_login_form(self, request, error_message='', extra_context=None): |
||||
request.session.set_test_cookie() |
||||
context = { |
||||
'title': _('Log in'), |
||||
'app_path': request.get_full_path(), |
||||
'error_message': error_message, |
||||
'root_path': self.root_path, |
||||
} |
||||
context.update(extra_context or {}) |
||||
return render_to_response(self.login_template or 'admin/login.html', context, |
||||
context_instance=template.RequestContext(request) |
||||
) |
||||
|
||||
def app_index(self, request, app_label, extra_context=None): |
||||
user = request.user |
||||
has_module_perms = user.has_module_perms(app_label) |
||||
app_dict = {} |
||||
for model, model_admin in self._registry.items(): |
||||
if app_label == model._meta.app_label: |
||||
if has_module_perms: |
||||
perms = { |
||||
'add': user.has_perm("%s.%s" % (app_label, model._meta.get_add_permission())), |
||||
'change': user.has_perm("%s.%s" % (app_label, model._meta.get_change_permission())), |
||||
'delete': user.has_perm("%s.%s" % (app_label, model._meta.get_delete_permission())), |
||||
} |
||||
# Check whether user has any perm for this module. |
||||
# If so, add the module to the model_list. |
||||
if True in perms.values(): |
||||
model_dict = { |
||||
'name': capfirst(model._meta.verbose_name_plural), |
||||
'admin_url': '%s/' % model.__name__.lower(), |
||||
'perms': perms, |
||||
} |
||||
if app_dict: |
||||
app_dict['models'].append(model_dict), |
||||
else: |
||||
# First time around, now that we know there's |
||||
# something to display, add in the necessary meta |
||||
# information. |
||||
app_dict = { |
||||
'name': app_label.title(), |
||||
'app_url': '', |
||||
'has_module_perms': has_module_perms, |
||||
'models': [model_dict], |
||||
} |
||||
if not app_dict: |
||||
raise http.Http404('The requested admin page does not exist.') |
||||
# Sort the models alphabetically within each app. |
||||
app_dict['models'].sort(lambda x, y: cmp(x['name'], y['name'])) |
||||
context = { |
||||
'title': _('%s administration') % capfirst(app_label), |
||||
'app_list': [app_dict], |
||||
'root_path': self.root_path, |
||||
} |
||||
context.update(extra_context or {}) |
||||
return render_to_response(self.app_index_template or 'admin/app_index.html', context, |
||||
context_instance=template.RequestContext(request) |
||||
) |
||||
|
||||
# This global object represents the default admin site, for the common case. |
||||
# You can instantiate AdminSite in your own code to create a custom admin site. |
||||
site = AdminSite() |
||||
@ -0,0 +1,316 @@
@@ -0,0 +1,316 @@
|
||||
from django.conf import settings |
||||
from django.contrib.admin.views.main import ALL_VAR, EMPTY_CHANGELIST_VALUE |
||||
from django.contrib.admin.views.main import ORDER_VAR, ORDER_TYPE_VAR, PAGE_VAR, SEARCH_VAR |
||||
from django.core.exceptions import ObjectDoesNotExist |
||||
from django.db import models |
||||
from django.utils import dateformat |
||||
from django.utils.html import escape, conditional_escape |
||||
from django.utils.text import capfirst |
||||
from django.utils.safestring import mark_safe |
||||
from django.utils.translation import get_date_formats, get_partial_date_formats, ugettext as _ |
||||
from django.utils.encoding import smart_unicode, smart_str, force_unicode |
||||
from django.template import Library |
||||
import datetime |
||||
|
||||
register = Library() |
||||
|
||||
DOT = '.' |
||||
|
||||
def paginator_number(cl,i): |
||||
if i == DOT: |
||||
return u'... ' |
||||
elif i == cl.page_num: |
||||
return mark_safe(u'<span class="this-page">%d</span> ' % (i+1)) |
||||
else: |
||||
return mark_safe(u'<a href="%s"%s>%d</a> ' % (cl.get_query_string({PAGE_VAR: i}), (i == cl.paginator.num_pages-1 and ' class="end"' or ''), i+1)) |
||||
paginator_number = register.simple_tag(paginator_number) |
||||
|
||||
def pagination(cl): |
||||
paginator, page_num = cl.paginator, cl.page_num |
||||
|
||||
pagination_required = (not cl.show_all or not cl.can_show_all) and cl.multi_page |
||||
if not pagination_required: |
||||
page_range = [] |
||||
else: |
||||
ON_EACH_SIDE = 3 |
||||
ON_ENDS = 2 |
||||
|
||||
# If there are 10 or fewer pages, display links to every page. |
||||
# Otherwise, do some fancy |
||||
if paginator.num_pages <= 10: |
||||
page_range = range(paginator.num_pages) |
||||
else: |
||||
# Insert "smart" pagination links, so that there are always ON_ENDS |
||||
# links at either end of the list of pages, and there are always |
||||
# ON_EACH_SIDE links at either end of the "current page" link. |
||||
page_range = [] |
||||
if page_num > (ON_EACH_SIDE + ON_ENDS): |
||||
page_range.extend(range(0, ON_EACH_SIDE - 1)) |
||||
page_range.append(DOT) |
||||
page_range.extend(range(page_num - ON_EACH_SIDE, page_num + 1)) |
||||
else: |
||||
page_range.extend(range(0, page_num + 1)) |
||||
if page_num < (paginator.num_pages - ON_EACH_SIDE - ON_ENDS - 1): |
||||
page_range.extend(range(page_num + 1, page_num + ON_EACH_SIDE + 1)) |
||||
page_range.append(DOT) |
||||
page_range.extend(range(paginator.num_pages - ON_ENDS, paginator.num_pages)) |
||||
else: |
||||
page_range.extend(range(page_num + 1, paginator.num_pages)) |
||||
|
||||
need_show_all_link = cl.can_show_all and not cl.show_all and cl.multi_page |
||||
return { |
||||
'cl': cl, |
||||
'pagination_required': pagination_required, |
||||
'show_all_url': need_show_all_link and cl.get_query_string({ALL_VAR: ''}), |
||||
'page_range': page_range, |
||||
'ALL_VAR': ALL_VAR, |
||||
'1': 1, |
||||
} |
||||
pagination = register.inclusion_tag('admin/pagination.html')(pagination) |
||||
|
||||
def result_headers(cl): |
||||
lookup_opts = cl.lookup_opts |
||||
|
||||
for i, field_name in enumerate(cl.list_display): |
||||
attr = None |
||||
try: |
||||
f = lookup_opts.get_field(field_name) |
||||
admin_order_field = None |
||||
except models.FieldDoesNotExist: |
||||
# For non-field list_display values, check for the function |
||||
# attribute "short_description". If that doesn't exist, fall back |
||||
# to the method name. And __str__ and __unicode__ are special-cases. |
||||
if field_name == '__unicode__': |
||||
header = force_unicode(lookup_opts.verbose_name) |
||||
elif field_name == '__str__': |
||||
header = smart_str(lookup_opts.verbose_name) |
||||
else: |
||||
if callable(field_name): |
||||
attr = field_name # field_name can be a callable |
||||
else: |
||||
try: |
||||
attr = getattr(cl.model_admin, field_name) |
||||
except AttributeError: |
||||
try: |
||||
attr = getattr(cl.model, field_name) |
||||
except AttributeError: |
||||
raise AttributeError, \ |
||||
"'%s' model or '%s' objects have no attribute '%s'" % \ |
||||
(lookup_opts.object_name, cl.model_admin.__class__, field_name) |
||||
|
||||
try: |
||||
header = attr.short_description |
||||
except AttributeError: |
||||
if callable(field_name): |
||||
header = field_name.__name__ |
||||
else: |
||||
header = field_name |
||||
header = header.replace('_', ' ') |
||||
|
||||
# It is a non-field, but perhaps one that is sortable |
||||
admin_order_field = getattr(attr, "admin_order_field", None) |
||||
if not admin_order_field: |
||||
yield {"text": header} |
||||
continue |
||||
|
||||
# So this _is_ a sortable non-field. Go to the yield |
||||
# after the else clause. |
||||
else: |
||||
if isinstance(f.rel, models.ManyToOneRel) and f.null: |
||||
yield {"text": f.verbose_name} |
||||
continue |
||||
else: |
||||
header = f.verbose_name |
||||
|
||||
th_classes = [] |
||||
new_order_type = 'asc' |
||||
if field_name == cl.order_field or admin_order_field == cl.order_field: |
||||
th_classes.append('sorted %sending' % cl.order_type.lower()) |
||||
new_order_type = {'asc': 'desc', 'desc': 'asc'}[cl.order_type.lower()] |
||||
|
||||
yield {"text": header, |
||||
"sortable": True, |
||||
"url": cl.get_query_string({ORDER_VAR: i, ORDER_TYPE_VAR: new_order_type}), |
||||
"class_attrib": mark_safe(th_classes and ' class="%s"' % ' '.join(th_classes) or '')} |
||||
|
||||
def _boolean_icon(field_val): |
||||
BOOLEAN_MAPPING = {True: 'yes', False: 'no', None: 'unknown'} |
||||
return mark_safe(u'<img src="%simg/admin/icon-%s.gif" alt="%s" />' % (settings.ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val)) |
||||
|
||||
def items_for_result(cl, result): |
||||
first = True |
||||
pk = cl.lookup_opts.pk.attname |
||||
for field_name in cl.list_display: |
||||
row_class = '' |
||||
try: |
||||
f = cl.lookup_opts.get_field(field_name) |
||||
except models.FieldDoesNotExist: |
||||
# For non-field list_display values, the value is either a method, |
||||
# property or returned via a callable. |
||||
try: |
||||
if callable(field_name): |
||||
attr = field_name |
||||
value = attr(result) |
||||
elif hasattr(cl.model_admin, field_name) and \ |
||||
not field_name == '__str__' and not field_name == '__unicode__': |
||||
attr = getattr(cl.model_admin, field_name) |
||||
value = attr(result) |
||||
else: |
||||
attr = getattr(result, field_name) |
||||
if callable(attr): |
||||
value = attr() |
||||
else: |
||||
value = attr |
||||
allow_tags = getattr(attr, 'allow_tags', False) |
||||
boolean = getattr(attr, 'boolean', False) |
||||
if boolean: |
||||
allow_tags = True |
||||
result_repr = _boolean_icon(value) |
||||
else: |
||||
result_repr = smart_unicode(value) |
||||
except (AttributeError, ObjectDoesNotExist): |
||||
result_repr = EMPTY_CHANGELIST_VALUE |
||||
else: |
||||
# Strip HTML tags in the resulting text, except if the |
||||
# function has an "allow_tags" attribute set to True. |
||||
if not allow_tags: |
||||
result_repr = escape(result_repr) |
||||
else: |
||||
result_repr = mark_safe(result_repr) |
||||
else: |
||||
field_val = getattr(result, f.attname) |
||||
|
||||
if isinstance(f.rel, models.ManyToOneRel): |
||||
if field_val is not None: |
||||
result_repr = escape(getattr(result, f.name)) |
||||
else: |
||||
result_repr = EMPTY_CHANGELIST_VALUE |
||||
# Dates and times are special: They're formatted in a certain way. |
||||
elif isinstance(f, models.DateField) or isinstance(f, models.TimeField): |
||||
if field_val: |
||||
(date_format, datetime_format, time_format) = get_date_formats() |
||||
if isinstance(f, models.DateTimeField): |
||||
result_repr = capfirst(dateformat.format(field_val, datetime_format)) |
||||
elif isinstance(f, models.TimeField): |
||||
result_repr = capfirst(dateformat.time_format(field_val, time_format)) |
||||
else: |
||||
result_repr = capfirst(dateformat.format(field_val, date_format)) |
||||
else: |
||||
result_repr = EMPTY_CHANGELIST_VALUE |
||||
row_class = ' class="nowrap"' |
||||
# Booleans are special: We use images. |
||||
elif isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField): |
||||
result_repr = _boolean_icon(field_val) |
||||
# DecimalFields are special: Zero-pad the decimals. |
||||
elif isinstance(f, models.DecimalField): |
||||
if field_val is not None: |
||||
result_repr = ('%%.%sf' % f.decimal_places) % field_val |
||||
else: |
||||
result_repr = EMPTY_CHANGELIST_VALUE |
||||
# Fields with choices are special: Use the representation |
||||
# of the choice. |
||||
elif f.choices: |
||||
result_repr = dict(f.choices).get(field_val, EMPTY_CHANGELIST_VALUE) |
||||
else: |
||||
result_repr = escape(field_val) |
||||
if force_unicode(result_repr) == '': |
||||
result_repr = mark_safe(' ') |
||||
# If list_display_links not defined, add the link tag to the first field |
||||
if (first and not cl.list_display_links) or field_name in cl.list_display_links: |
||||
table_tag = {True:'th', False:'td'}[first] |
||||
first = False |
||||
url = cl.url_for_result(result) |
||||
# Convert the pk to something that can be used in Javascript. |
||||
# Problem cases are long ints (23L) and non-ASCII strings. |
||||
if cl.to_field: |
||||
attr = str(cl.to_field) |
||||
else: |
||||
attr = pk |
||||
result_id = repr(force_unicode(getattr(result, attr)))[1:] |
||||
yield mark_safe(u'<%s%s><a href="%s"%s>%s</a></%s>' % \ |
||||
(table_tag, row_class, url, (cl.is_popup and ' onclick="opener.dismissRelatedLookupPopup(window, %s); return false;"' % result_id or ''), conditional_escape(result_repr), table_tag)) |
||||
else: |
||||
yield mark_safe(u'<td%s>%s</td>' % (row_class, conditional_escape(result_repr))) |
||||
|
||||
def results(cl): |
||||
for res in cl.result_list: |
||||
yield list(items_for_result(cl,res)) |
||||
|
||||
def result_list(cl): |
||||
return {'cl': cl, |
||||
'result_headers': list(result_headers(cl)), |
||||
'results': list(results(cl))} |
||||
result_list = register.inclusion_tag("admin/change_list_results.html")(result_list) |
||||
|
||||
def date_hierarchy(cl): |
||||
if cl.date_hierarchy: |
||||
field_name = cl.date_hierarchy |
||||
year_field = '%s__year' % field_name |
||||
month_field = '%s__month' % field_name |
||||
day_field = '%s__day' % field_name |
||||
field_generic = '%s__' % field_name |
||||
year_lookup = cl.params.get(year_field) |
||||
month_lookup = cl.params.get(month_field) |
||||
day_lookup = cl.params.get(day_field) |
||||
year_month_format, month_day_format = get_partial_date_formats() |
||||
|
||||
link = lambda d: mark_safe(cl.get_query_string(d, [field_generic])) |
||||
|
||||
if year_lookup and month_lookup and day_lookup: |
||||
day = datetime.date(int(year_lookup), int(month_lookup), int(day_lookup)) |
||||
return { |
||||
'show': True, |
||||
'back': { |
||||
'link': link({year_field: year_lookup, month_field: month_lookup}), |
||||
'title': dateformat.format(day, year_month_format) |
||||
}, |
||||
'choices': [{'title': dateformat.format(day, month_day_format)}] |
||||
} |
||||
elif year_lookup and month_lookup: |
||||
days = cl.query_set.filter(**{year_field: year_lookup, month_field: month_lookup}).dates(field_name, 'day') |
||||
return { |
||||
'show': True, |
||||
'back': { |
||||
'link': link({year_field: year_lookup}), |
||||
'title': year_lookup |
||||
}, |
||||
'choices': [{ |
||||
'link': link({year_field: year_lookup, month_field: month_lookup, day_field: day.day}), |
||||
'title': dateformat.format(day, month_day_format) |
||||
} for day in days] |
||||
} |
||||
elif year_lookup: |
||||
months = cl.query_set.filter(**{year_field: year_lookup}).dates(field_name, 'month') |
||||
return { |
||||
'show' : True, |
||||
'back': { |
||||
'link' : link({}), |
||||
'title': _('All dates') |
||||
}, |
||||
'choices': [{ |
||||
'link': link({year_field: year_lookup, month_field: month.month}), |
||||
'title': dateformat.format(month, year_month_format) |
||||
} for month in months] |
||||
} |
||||
else: |
||||
years = cl.query_set.dates(field_name, 'year') |
||||
return { |
||||
'show': True, |
||||
'choices': [{ |
||||
'link': link({year_field: year.year}), |
||||
'title': year.year |
||||
} for year in years] |
||||
} |
||||
date_hierarchy = register.inclusion_tag('admin/date_hierarchy.html')(date_hierarchy) |
||||
|
||||
def search_form(cl): |
||||
return { |
||||
'cl': cl, |
||||
'show_result_count': cl.result_count != cl.full_result_count, |
||||
'search_var': SEARCH_VAR |
||||
} |
||||
search_form = register.inclusion_tag('admin/search_form.html')(search_form) |
||||
|
||||
def admin_list_filter(cl, spec): |
||||
return {'title': spec.title(), 'choices' : list(spec.choices(cl))} |
||||
admin_list_filter = register.inclusion_tag('admin/filter.html')(admin_list_filter) |
||||
@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
|
||||
from django import template |
||||
|
||||
register = template.Library() |
||||
|
||||
def prepopulated_fields_js(context): |
||||
""" |
||||
Creates a list of prepopulated_fields that should render Javascript for |
||||
the prepopulated fields for both the admin form and inlines. |
||||
""" |
||||
prepopulated_fields = [] |
||||
if context['add'] and 'adminform' in context: |
||||
prepopulated_fields.extend(context['adminform'].prepopulated_fields) |
||||
if 'inline_admin_formsets' in context: |
||||
for inline_admin_formset in context['inline_admin_formsets']: |
||||
for inline_admin_form in inline_admin_formset: |
||||
if inline_admin_form.original is None: |
||||
prepopulated_fields.extend(inline_admin_form.prepopulated_fields) |
||||
context.update({'prepopulated_fields': prepopulated_fields}) |
||||
return context |
||||
prepopulated_fields_js = register.inclusion_tag('admin/prepopulated_fields_js.html', takes_context=True)(prepopulated_fields_js) |
||||
|
||||
def submit_row(context): |
||||
opts = context['opts'] |
||||
change = context['change'] |
||||
is_popup = context['is_popup'] |
||||
save_as = context['save_as'] |
||||
return { |
||||
'onclick_attrib': (opts.get_ordered_objects() and change |
||||
and 'onclick="submitOrderForm();"' or ''), |
||||
'show_delete_link': (not is_popup and context['has_delete_permission'] |
||||
and (change or context['show_delete'])), |
||||
'show_save_as_new': not is_popup and change and save_as, |
||||
'show_save_and_add_another': context['has_add_permission'] and |
||||
not is_popup and (not save_as or context['add']), |
||||
'show_save_and_continue': not is_popup and context['has_change_permission'], |
||||
'is_popup': is_popup, |
||||
'show_save': True |
||||
} |
||||
submit_row = register.inclusion_tag('admin/submit_line.html', takes_context=True)(submit_row) |
||||
@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
|
||||
from django.template import Library |
||||
|
||||
register = Library() |
||||
|
||||
def admin_media_prefix(): |
||||
""" |
||||
Returns the string contained in the setting ADMIN_MEDIA_PREFIX. |
||||
""" |
||||
try: |
||||
from django.conf import settings |
||||
except ImportError: |
||||
return '' |
||||
return settings.ADMIN_MEDIA_PREFIX |
||||
admin_media_prefix = register.simple_tag(admin_media_prefix) |
||||
@ -0,0 +1,56 @@
@@ -0,0 +1,56 @@
|
||||
from django import template |
||||
from django.contrib.admin.models import LogEntry |
||||
|
||||
register = template.Library() |
||||
|
||||
class AdminLogNode(template.Node): |
||||
def __init__(self, limit, varname, user): |
||||
self.limit, self.varname, self.user = limit, varname, user |
||||
|
||||
def __repr__(self): |
||||
return "<GetAdminLog Node>" |
||||
|
||||
def render(self, context): |
||||
if self.user is None: |
||||
context[self.varname] = LogEntry.objects.all().select_related()[:self.limit] |
||||
else: |
||||
if not self.user.isdigit(): |
||||
self.user = context[self.user].id |
||||
context[self.varname] = LogEntry.objects.filter(user__id__exact=self.user).select_related()[:self.limit] |
||||
return '' |
||||
|
||||
class DoGetAdminLog: |
||||
""" |
||||
Populates a template variable with the admin log for the given criteria. |
||||
|
||||
Usage:: |
||||
|
||||
{% get_admin_log [limit] as [varname] for_user [context_var_containing_user_obj] %} |
||||
|
||||
Examples:: |
||||
|
||||
{% get_admin_log 10 as admin_log for_user 23 %} |
||||
{% get_admin_log 10 as admin_log for_user user %} |
||||
{% get_admin_log 10 as admin_log %} |
||||
|
||||
Note that ``context_var_containing_user_obj`` can be a hard-coded integer |
||||
(user ID) or the name of a template context variable containing the user |
||||
object whose ID you want. |
||||
""" |
||||
def __init__(self, tag_name): |
||||
self.tag_name = tag_name |
||||
|
||||
def __call__(self, parser, token): |
||||
tokens = token.contents.split() |
||||
if len(tokens) < 4: |
||||
raise template.TemplateSyntaxError, "'%s' statements require two arguments" % self.tag_name |
||||
if not tokens[1].isdigit(): |
||||
raise template.TemplateSyntaxError, "First argument in '%s' must be an integer" % self.tag_name |
||||
if tokens[2] != 'as': |
||||
raise template.TemplateSyntaxError, "Second argument in '%s' must be 'as'" % self.tag_name |
||||
if len(tokens) > 4: |
||||
if tokens[4] != 'for_user': |
||||
raise template.TemplateSyntaxError, "Fourth argument in '%s' must be 'for_user'" % self.tag_name |
||||
return AdminLogNode(limit=tokens[1], varname=tokens[3], user=(len(tokens) > 5 and tokens[5] or None)) |
||||
|
||||
register.tag('get_admin_log', DoGetAdminLog('get_admin_log')) |
||||
@ -0,0 +1,151 @@
@@ -0,0 +1,151 @@
|
||||
from django.core.exceptions import ObjectDoesNotExist |
||||
from django.db import models |
||||
from django.utils.html import escape |
||||
from django.utils.safestring import mark_safe |
||||
from django.utils.text import capfirst |
||||
from django.utils.encoding import force_unicode |
||||
from django.utils.translation import ugettext as _ |
||||
|
||||
|
||||
def quote(s): |
||||
""" |
||||
Ensure that primary key values do not confuse the admin URLs by escaping |
||||
any '/', '_' and ':' characters. Similar to urllib.quote, except that the |
||||
quoting is slightly different so that it doesn't get automatically |
||||
unquoted by the Web browser. |
||||
""" |
||||
if not isinstance(s, basestring): |
||||
return s |
||||
res = list(s) |
||||
for i in range(len(res)): |
||||
c = res[i] |
||||
if c in """:/_#?;@&=+$,"<>%\\""": |
||||
res[i] = '_%02X' % ord(c) |
||||
return ''.join(res) |
||||
|
||||
def unquote(s): |
||||
""" |
||||
Undo the effects of quote(). Based heavily on urllib.unquote(). |
||||
""" |
||||
mychr = chr |
||||
myatoi = int |
||||
list = s.split('_') |
||||
res = [list[0]] |
||||
myappend = res.append |
||||
del list[0] |
||||
for item in list: |
||||
if item[1:2]: |
||||
try: |
||||
myappend(mychr(myatoi(item[:2], 16)) + item[2:]) |
||||
except ValueError: |
||||
myappend('_' + item) |
||||
else: |
||||
myappend('_' + item) |
||||
return "".join(res) |
||||
|
||||
def flatten_fieldsets(fieldsets): |
||||
"""Returns a list of field names from an admin fieldsets structure.""" |
||||
field_names = [] |
||||
for name, opts in fieldsets: |
||||
for field in opts['fields']: |
||||
# type checking feels dirty, but it seems like the best way here |
||||
if type(field) == tuple: |
||||
field_names.extend(field) |
||||
else: |
||||
field_names.append(field) |
||||
return field_names |
||||
|
||||
def _nest_help(obj, depth, val): |
||||
current = obj |
||||
for i in range(depth): |
||||
current = current[-1] |
||||
current.append(val) |
||||
|
||||
def get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current_depth, admin_site): |
||||
"Helper function that recursively populates deleted_objects." |
||||
nh = _nest_help # Bind to local variable for performance |
||||
if current_depth > 16: |
||||
return # Avoid recursing too deep. |
||||
opts_seen = [] |
||||
for related in opts.get_all_related_objects(): |
||||
has_admin = related.model in admin_site._registry |
||||
if related.opts in opts_seen: |
||||
continue |
||||
opts_seen.append(related.opts) |
||||
rel_opts_name = related.get_accessor_name() |
||||
if isinstance(related.field.rel, models.OneToOneRel): |
||||
try: |
||||
sub_obj = getattr(obj, rel_opts_name) |
||||
except ObjectDoesNotExist: |
||||
pass |
||||
else: |
||||
if has_admin: |
||||
p = '%s.%s' % (related.opts.app_label, related.opts.get_delete_permission()) |
||||
if not user.has_perm(p): |
||||
perms_needed.add(related.opts.verbose_name) |
||||
# We don't care about populating deleted_objects now. |
||||
continue |
||||
if not has_admin: |
||||
# Don't display link to edit, because it either has no |
||||
# admin or is edited inline. |
||||
nh(deleted_objects, current_depth, [u'%s: %s' % (force_unicode(capfirst(related.opts.verbose_name)), sub_obj), []]) |
||||
else: |
||||
# Display a link to the admin page. |
||||
nh(deleted_objects, current_depth, [mark_safe(u'%s: <a href="../../../../%s/%s/%s/">%s</a>' % |
||||
(escape(force_unicode(capfirst(related.opts.verbose_name))), |
||||
related.opts.app_label, |
||||
related.opts.object_name.lower(), |
||||
sub_obj._get_pk_val(), sub_obj)), []]) |
||||
get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2, admin_site) |
||||
else: |
||||
has_related_objs = False |
||||
for sub_obj in getattr(obj, rel_opts_name).all(): |
||||
has_related_objs = True |
||||
if not has_admin: |
||||
# Don't display link to edit, because it either has no |
||||
# admin or is edited inline. |
||||
nh(deleted_objects, current_depth, [u'%s: %s' % (force_unicode(capfirst(related.opts.verbose_name)), sub_obj), []]) |
||||
else: |
||||
# Display a link to the admin page. |
||||
nh(deleted_objects, current_depth, [mark_safe(u'%s: <a href="../../../../%s/%s/%s/">%s</a>' % \ |
||||
(escape(force_unicode(capfirst(related.opts.verbose_name))), related.opts.app_label, related.opts.object_name.lower(), sub_obj._get_pk_val(), escape(sub_obj))), []]) |
||||
get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2, admin_site) |
||||
# If there were related objects, and the user doesn't have |
||||
# permission to delete them, add the missing perm to perms_needed. |
||||
if has_admin and has_related_objs: |
||||
p = '%s.%s' % (related.opts.app_label, related.opts.get_delete_permission()) |
||||
if not user.has_perm(p): |
||||
perms_needed.add(related.opts.verbose_name) |
||||
for related in opts.get_all_related_many_to_many_objects(): |
||||
has_admin = related.model in admin_site._registry |
||||
if related.opts in opts_seen: |
||||
continue |
||||
opts_seen.append(related.opts) |
||||
rel_opts_name = related.get_accessor_name() |
||||
has_related_objs = False |
||||
|
||||
# related.get_accessor_name() could return None for symmetrical relationships |
||||
if rel_opts_name: |
||||
rel_objs = getattr(obj, rel_opts_name, None) |
||||
if rel_objs: |
||||
has_related_objs = True |
||||
|
||||
if has_related_objs: |
||||
for sub_obj in rel_objs.all(): |
||||
if not has_admin: |
||||
# Don't display link to edit, because it either has no |
||||
# admin or is edited inline. |
||||
nh(deleted_objects, current_depth, [_('One or more %(fieldname)s in %(name)s: %(obj)s') % \ |
||||
{'fieldname': force_unicode(related.field.verbose_name), 'name': force_unicode(related.opts.verbose_name), 'obj': escape(sub_obj)}, []]) |
||||
else: |
||||
# Display a link to the admin page. |
||||
nh(deleted_objects, current_depth, [ |
||||
mark_safe((_('One or more %(fieldname)s in %(name)s:') % {'fieldname': escape(force_unicode(related.field.verbose_name)), 'name': escape(force_unicode(related.opts.verbose_name))}) + \ |
||||
(u' <a href="../../../../%s/%s/%s/">%s</a>' % \ |
||||
(related.opts.app_label, related.opts.module_name, sub_obj._get_pk_val(), escape(sub_obj)))), []]) |
||||
# If there were related objects, and the user doesn't have |
||||
# permission to change them, add the missing perm to perms_needed. |
||||
if has_admin and has_related_objs: |
||||
p = u'%s.%s' % (related.opts.app_label, related.opts.get_change_permission()) |
||||
if not user.has_perm(p): |
||||
perms_needed.add(related.opts.verbose_name) |
||||
@ -0,0 +1,277 @@
@@ -0,0 +1,277 @@
|
||||
try: |
||||
set |
||||
except NameError: |
||||
from sets import Set as set # Python 2.3 fallback |
||||
|
||||
from django.core.exceptions import ImproperlyConfigured |
||||
from django.db import models |
||||
from django.forms.models import BaseModelForm, BaseModelFormSet, fields_for_model |
||||
from django.contrib.admin.options import flatten_fieldsets, BaseModelAdmin |
||||
from django.contrib.admin.options import HORIZONTAL, VERTICAL |
||||
|
||||
__all__ = ['validate'] |
||||
|
||||
def validate(cls, model): |
||||
""" |
||||
Does basic ModelAdmin option validation. Calls custom validation |
||||
classmethod in the end if it is provided in cls. The signature of the |
||||
custom validation classmethod should be: def validate(cls, model). |
||||
""" |
||||
# Before we can introspect models, they need to be fully loaded so that |
||||
# inter-relations are set up correctly. We force that here. |
||||
models.get_apps() |
||||
|
||||
opts = model._meta |
||||
validate_base(cls, model) |
||||
|
||||
# list_display |
||||
if hasattr(cls, 'list_display'): |
||||
check_isseq(cls, 'list_display', cls.list_display) |
||||
for idx, field in enumerate(cls.list_display): |
||||
if not callable(field): |
||||
if not hasattr(cls, field): |
||||
if not hasattr(model, field): |
||||
try: |
||||
opts.get_field(field) |
||||
except models.FieldDoesNotExist: |
||||
raise ImproperlyConfigured("%s.list_display[%d], %r is not a callable or an attribute of %r or found in the model %r." |
||||
% (cls.__name__, idx, field, cls.__name__, model._meta.object_name)) |
||||
f = fetch_attr(cls, model, opts, "list_display[%d]" % idx, field) |
||||
if isinstance(f, models.ManyToManyField): |
||||
raise ImproperlyConfigured("'%s.list_display[%d]', '%s' is a ManyToManyField which is not supported." |
||||
% (cls.__name__, idx, field)) |
||||
|
||||
# list_display_links |
||||
if hasattr(cls, 'list_display_links'): |
||||
check_isseq(cls, 'list_display_links', cls.list_display_links) |
||||
for idx, field in enumerate(cls.list_display_links): |
||||
fetch_attr(cls, model, opts, 'list_display_links[%d]' % idx, field) |
||||
if field not in cls.list_display: |
||||
raise ImproperlyConfigured("'%s.list_display_links[%d]'" |
||||
"refers to '%s' which is not defined in 'list_display'." |
||||
% (cls.__name__, idx, field)) |
||||
|
||||
# list_filter |
||||
if hasattr(cls, 'list_filter'): |
||||
check_isseq(cls, 'list_filter', cls.list_filter) |
||||
for idx, field in enumerate(cls.list_filter): |
||||
get_field(cls, model, opts, 'list_filter[%d]' % idx, field) |
||||
|
||||
# list_per_page = 100 |
||||
if hasattr(cls, 'list_per_page') and not isinstance(cls.list_per_page, int): |
||||
raise ImproperlyConfigured("'%s.list_per_page' should be a integer." |
||||
% cls.__name__) |
||||
|
||||
# search_fields = () |
||||
if hasattr(cls, 'search_fields'): |
||||
check_isseq(cls, 'search_fields', cls.search_fields) |
||||
|
||||
# date_hierarchy = None |
||||
if cls.date_hierarchy: |
||||
f = get_field(cls, model, opts, 'date_hierarchy', cls.date_hierarchy) |
||||
if not isinstance(f, (models.DateField, models.DateTimeField)): |
||||
raise ImproperlyConfigured("'%s.date_hierarchy is " |
||||
"neither an instance of DateField nor DateTimeField." |
||||
% cls.__name__) |
||||
|
||||
# ordering = None |
||||
if cls.ordering: |
||||
check_isseq(cls, 'ordering', cls.ordering) |
||||
for idx, field in enumerate(cls.ordering): |
||||
if field == '?' and len(cls.ordering) != 1: |
||||
raise ImproperlyConfigured("'%s.ordering' has the random " |
||||
"ordering marker '?', but contains other fields as " |
||||
"well. Please either remove '?' or the other fields." |
||||
% cls.__name__) |
||||
if field == '?': |
||||
continue |
||||
if field.startswith('-'): |
||||
field = field[1:] |
||||
# Skip ordering in the format field1__field2 (FIXME: checking |
||||
# this format would be nice, but it's a little fiddly). |
||||
if '__' in field: |
||||
continue |
||||
get_field(cls, model, opts, 'ordering[%d]' % idx, field) |
||||
|
||||
# list_select_related = False |
||||
# save_as = False |
||||
# save_on_top = False |
||||
for attr in ('list_select_related', 'save_as', 'save_on_top'): |
||||
if not isinstance(getattr(cls, attr), bool): |
||||
raise ImproperlyConfigured("'%s.%s' should be a boolean." |
||||
% (cls.__name__, attr)) |
||||
|
||||
# inlines = [] |
||||
if hasattr(cls, 'inlines'): |
||||
check_isseq(cls, 'inlines', cls.inlines) |
||||
for idx, inline in enumerate(cls.inlines): |
||||
if not issubclass(inline, BaseModelAdmin): |
||||
raise ImproperlyConfigured("'%s.inlines[%d]' does not inherit " |
||||
"from BaseModelAdmin." % (cls.__name__, idx)) |
||||
if not inline.model: |
||||
raise ImproperlyConfigured("'model' is a required attribute " |
||||
"of '%s.inlines[%d]'." % (cls.__name__, idx)) |
||||
if not issubclass(inline.model, models.Model): |
||||
raise ImproperlyConfigured("'%s.inlines[%d].model' does not " |
||||
"inherit from models.Model." % (cls.__name__, idx)) |
||||
validate_base(inline, inline.model) |
||||
validate_inline(inline) |
||||
|
||||
def validate_inline(cls): |
||||
# model is already verified to exist and be a Model |
||||
if cls.fk_name: # default value is None |
||||
f = get_field(cls, cls.model, cls.model._meta, |
||||
'fk_name', cls.fk_name) |
||||
if not isinstance(f, models.ForeignKey): |
||||
raise ImproperlyConfigured("'%s.fk_name is not an instance of " |
||||
"models.ForeignKey." % cls.__name__) |
||||
# extra = 3 |
||||
# max_num = 0 |
||||
for attr in ('extra', 'max_num'): |
||||
if not isinstance(getattr(cls, attr), int): |
||||
raise ImproperlyConfigured("'%s.%s' should be a integer." |
||||
% (cls.__name__, attr)) |
||||
|
||||
# formset |
||||
if hasattr(cls, 'formset') and not issubclass(cls.formset, BaseModelFormSet): |
||||
raise ImproperlyConfigured("'%s.formset' does not inherit from " |
||||
"BaseModelFormSet." % cls.__name__) |
||||
|
||||
def validate_base(cls, model): |
||||
opts = model._meta |
||||
|
||||
# raw_id_fields |
||||
if hasattr(cls, 'raw_id_fields'): |
||||
check_isseq(cls, 'raw_id_fields', cls.raw_id_fields) |
||||
for idx, field in enumerate(cls.raw_id_fields): |
||||
f = get_field(cls, model, opts, 'raw_id_fields', field) |
||||
if not isinstance(f, (models.ForeignKey, models.ManyToManyField)): |
||||
raise ImproperlyConfigured("'%s.raw_id_fields[%d]', '%s' must " |
||||
"be either a ForeignKey or ManyToManyField." |
||||
% (cls.__name__, idx, field)) |
||||
|
||||
# fields |
||||
if cls.fields: # default value is None |
||||
check_isseq(cls, 'fields', cls.fields) |
||||
for field in cls.fields: |
||||
check_formfield(cls, model, opts, 'fields', field) |
||||
if cls.fieldsets: |
||||
raise ImproperlyConfigured('Both fieldsets and fields are specified in %s.' % cls.__name__) |
||||
if len(cls.fields) > len(set(cls.fields)): |
||||
raise ImproperlyConfigured('There are duplicate field(s) in %s.fields' % cls.__name__) |
||||
|
||||
# fieldsets |
||||
if cls.fieldsets: # default value is None |
||||
check_isseq(cls, 'fieldsets', cls.fieldsets) |
||||
for idx, fieldset in enumerate(cls.fieldsets): |
||||
check_isseq(cls, 'fieldsets[%d]' % idx, fieldset) |
||||
if len(fieldset) != 2: |
||||
raise ImproperlyConfigured("'%s.fieldsets[%d]' does not " |
||||
"have exactly two elements." % (cls.__name__, idx)) |
||||
check_isdict(cls, 'fieldsets[%d][1]' % idx, fieldset[1]) |
||||
if 'fields' not in fieldset[1]: |
||||
raise ImproperlyConfigured("'fields' key is required in " |
||||
"%s.fieldsets[%d][1] field options dict." |
||||
% (cls.__name__, idx)) |
||||
flattened_fieldsets = flatten_fieldsets(cls.fieldsets) |
||||
if len(flattened_fieldsets) > len(set(flattened_fieldsets)): |
||||
raise ImproperlyConfigured('There are duplicate field(s) in %s.fieldsets' % cls.__name__) |
||||
for field in flattened_fieldsets: |
||||
check_formfield(cls, model, opts, "fieldsets[%d][1]['fields']" % idx, field) |
||||
|
||||
# form |
||||
if hasattr(cls, 'form') and not issubclass(cls.form, BaseModelForm): |
||||
raise ImproperlyConfigured("%s.form does not inherit from " |
||||
"BaseModelForm." % cls.__name__) |
||||
|
||||
# filter_vertical |
||||
if hasattr(cls, 'filter_vertical'): |
||||
check_isseq(cls, 'filter_vertical', cls.filter_vertical) |
||||
for idx, field in enumerate(cls.filter_vertical): |
||||
f = get_field(cls, model, opts, 'filter_vertical', field) |
||||
if not isinstance(f, models.ManyToManyField): |
||||
raise ImproperlyConfigured("'%s.filter_vertical[%d]' must be " |
||||
"a ManyToManyField." % (cls.__name__, idx)) |
||||
|
||||
# filter_horizontal |
||||
if hasattr(cls, 'filter_horizontal'): |
||||
check_isseq(cls, 'filter_horizontal', cls.filter_horizontal) |
||||
for idx, field in enumerate(cls.filter_horizontal): |
||||
f = get_field(cls, model, opts, 'filter_horizontal', field) |
||||
if not isinstance(f, models.ManyToManyField): |
||||
raise ImproperlyConfigured("'%s.filter_horizontal[%d]' must be " |
||||
"a ManyToManyField." % (cls.__name__, idx)) |
||||
|
||||
# radio_fields |
||||
if hasattr(cls, 'radio_fields'): |
||||
check_isdict(cls, 'radio_fields', cls.radio_fields) |
||||
for field, val in cls.radio_fields.items(): |
||||
f = get_field(cls, model, opts, 'radio_fields', field) |
||||
if not (isinstance(f, models.ForeignKey) or f.choices): |
||||
raise ImproperlyConfigured("'%s.radio_fields['%s']' " |
||||
"is neither an instance of ForeignKey nor does " |
||||
"have choices set." % (cls.__name__, field)) |
||||
if not val in (HORIZONTAL, VERTICAL): |
||||
raise ImproperlyConfigured("'%s.radio_fields['%s']' " |
||||
"is neither admin.HORIZONTAL nor admin.VERTICAL." |
||||
% (cls.__name__, field)) |
||||
|
||||
# prepopulated_fields |
||||
if hasattr(cls, 'prepopulated_fields'): |
||||
check_isdict(cls, 'prepopulated_fields', cls.prepopulated_fields) |
||||
for field, val in cls.prepopulated_fields.items(): |
||||
f = get_field(cls, model, opts, 'prepopulated_fields', field) |
||||
if isinstance(f, (models.DateTimeField, models.ForeignKey, |
||||
models.ManyToManyField)): |
||||
raise ImproperlyConfigured("'%s.prepopulated_fields['%s']' " |
||||
"is either a DateTimeField, ForeignKey or " |
||||
"ManyToManyField. This isn't allowed." |
||||
% (cls.__name__, field)) |
||||
check_isseq(cls, "prepopulated_fields['%s']" % field, val) |
||||
for idx, f in enumerate(val): |
||||
get_field(cls, model, |
||||
opts, "prepopulated_fields['%s'][%d]" |
||||
% (f, idx), f) |
||||
|
||||
def check_isseq(cls, label, obj): |
||||
if not isinstance(obj, (list, tuple)): |
||||
raise ImproperlyConfigured("'%s.%s' must be a list or tuple." |
||||
% (cls.__name__, label)) |
||||
|
||||
def check_isdict(cls, label, obj): |
||||
if not isinstance(obj, dict): |
||||
raise ImproperlyConfigured("'%s.%s' must be a dictionary." |
||||
% (cls.__name__, label)) |
||||
|
||||
def get_field(cls, model, opts, label, field): |
||||
try: |
||||
return opts.get_field(field) |
||||
except models.FieldDoesNotExist: |
||||
raise ImproperlyConfigured("'%s.%s' refers to field '%s' that is missing from model '%s'." |
||||
% (cls.__name__, label, field, model.__name__)) |
||||
|
||||
def check_formfield(cls, model, opts, label, field): |
||||
if getattr(cls.form, 'base_fields', None): |
||||
try: |
||||
cls.form.base_fields[field] |
||||
except KeyError: |
||||
raise ImproperlyConfigured("'%s.%s' refers to field '%s' that " |
||||
"is missing from the form." % (cls.__name__, label, field)) |
||||
else: |
||||
fields = fields_for_model(model) |
||||
try: |
||||
fields[field] |
||||
except KeyError: |
||||
raise ImproperlyConfigured("'%s.%s' refers to field '%s' that " |
||||
"is missing from the form." % (cls.__name__, label, field)) |
||||
|
||||
def fetch_attr(cls, model, opts, label, field): |
||||
try: |
||||
return opts.get_field(field) |
||||
except models.FieldDoesNotExist: |
||||
pass |
||||
try: |
||||
return getattr(model, field) |
||||
except AttributeError: |
||||
raise ImproperlyConfigured("'%s.%s' refers to '%s' that is neither a field, method or property of model '%s'." |
||||
% (cls.__name__, label, field, model.__name__)) |
||||
@ -0,0 +1,77 @@
@@ -0,0 +1,77 @@
|
||||
import base64 |
||||
try: |
||||
from functools import wraps |
||||
except ImportError: |
||||
from django.utils.functional import wraps # Python 2.3, 2.4 fallback. |
||||
|
||||
from django import http, template |
||||
from django.conf import settings |
||||
from django.contrib.auth.models import User |
||||
from django.contrib.auth import authenticate, login |
||||
from django.shortcuts import render_to_response |
||||
from django.utils.translation import ugettext_lazy, ugettext as _ |
||||
|
||||
ERROR_MESSAGE = ugettext_lazy("Please enter a correct username and password. Note that both fields are case-sensitive.") |
||||
LOGIN_FORM_KEY = 'this_is_the_login_form' |
||||
|
||||
def _display_login_form(request, error_message=''): |
||||
request.session.set_test_cookie() |
||||
return render_to_response('admin/login.html', { |
||||
'title': _('Log in'), |
||||
'app_path': request.get_full_path(), |
||||
'error_message': error_message |
||||
}, context_instance=template.RequestContext(request)) |
||||
|
||||
def staff_member_required(view_func): |
||||
""" |
||||
Decorator for views that checks that the user is logged in and is a staff |
||||
member, displaying the login page if necessary. |
||||
""" |
||||
def _checklogin(request, *args, **kwargs): |
||||
if request.user.is_authenticated() and request.user.is_staff: |
||||
# The user is valid. Continue to the admin page. |
||||
return view_func(request, *args, **kwargs) |
||||
|
||||
assert hasattr(request, 'session'), "The Django admin requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'." |
||||
|
||||
# If this isn't already the login page, display it. |
||||
if LOGIN_FORM_KEY not in request.POST: |
||||
if request.POST: |
||||
message = _("Please log in again, because your session has expired.") |
||||
else: |
||||
message = "" |
||||
return _display_login_form(request, message) |
||||
|
||||
# Check that the user accepts cookies. |
||||
if not request.session.test_cookie_worked(): |
||||
message = _("Looks like your browser isn't configured to accept cookies. Please enable cookies, reload this page, and try again.") |
||||
return _display_login_form(request, message) |
||||
else: |
||||
request.session.delete_test_cookie() |
||||
|
||||
# Check the password. |
||||
username = request.POST.get('username', None) |
||||
password = request.POST.get('password', None) |
||||
user = authenticate(username=username, password=password) |
||||
if user is None: |
||||
message = ERROR_MESSAGE |
||||
if '@' in username: |
||||
# Mistakenly entered e-mail address instead of username? Look it up. |
||||
users = list(User.objects.filter(email=username)) |
||||
if len(users) == 1 and users[0].check_password(password): |
||||
message = _("Your e-mail address is not your username. Try '%s' instead.") % users[0].username |
||||
else: |
||||
# Either we cannot find the user, or if more than 1 |
||||
# we cannot guess which user is the correct one. |
||||
message = _("Usernames cannot contain the '@' character.") |
||||
return _display_login_form(request, message) |
||||
|
||||
# The user data is correct; log in the user in and continue. |
||||
else: |
||||
if user.is_active and user.is_staff: |
||||
login(request, user) |
||||
return http.HttpResponseRedirect(request.get_full_path()) |
||||
else: |
||||
return _display_login_form(request, ERROR_MESSAGE) |
||||
|
||||
return wraps(view_func)(_checklogin) |
||||
@ -0,0 +1,241 @@
@@ -0,0 +1,241 @@
|
||||
from django.contrib.admin.filterspecs import FilterSpec |
||||
from django.contrib.admin.options import IncorrectLookupParameters |
||||
from django.contrib.admin.util import quote |
||||
from django.core.paginator import Paginator, InvalidPage |
||||
from django.db import models |
||||
from django.db.models.query import QuerySet |
||||
from django.utils.encoding import force_unicode, smart_str |
||||
from django.utils.translation import ugettext |
||||
from django.utils.http import urlencode |
||||
import operator |
||||
|
||||
try: |
||||
set |
||||
except NameError: |
||||
from sets import Set as set # Python 2.3 fallback |
||||
|
||||
# The system will display a "Show all" link on the change list only if the |
||||
# total result count is less than or equal to this setting. |
||||
MAX_SHOW_ALL_ALLOWED = 200 |
||||
|
||||
# Changelist settings |
||||
ALL_VAR = 'all' |
||||
ORDER_VAR = 'o' |
||||
ORDER_TYPE_VAR = 'ot' |
||||
PAGE_VAR = 'p' |
||||
SEARCH_VAR = 'q' |
||||
TO_FIELD_VAR = 't' |
||||
IS_POPUP_VAR = 'pop' |
||||
ERROR_FLAG = 'e' |
||||
|
||||
# Text to display within change-list table cells if the value is blank. |
||||
EMPTY_CHANGELIST_VALUE = '(None)' |
||||
|
||||
class ChangeList(object): |
||||
def __init__(self, request, model, list_display, list_display_links, list_filter, date_hierarchy, search_fields, list_select_related, list_per_page, model_admin): |
||||
self.model = model |
||||
self.opts = model._meta |
||||
self.lookup_opts = self.opts |
||||
self.root_query_set = model_admin.queryset(request) |
||||
self.list_display = list_display |
||||
self.list_display_links = list_display_links |
||||
self.list_filter = list_filter |
||||
self.date_hierarchy = date_hierarchy |
||||
self.search_fields = search_fields |
||||
self.list_select_related = list_select_related |
||||
self.list_per_page = list_per_page |
||||
self.model_admin = model_admin |
||||
|
||||
# Get search parameters from the query string. |
||||
try: |
||||
self.page_num = int(request.GET.get(PAGE_VAR, 0)) |
||||
except ValueError: |
||||
self.page_num = 0 |
||||
self.show_all = ALL_VAR in request.GET |
||||
self.is_popup = IS_POPUP_VAR in request.GET |
||||
self.to_field = request.GET.get(TO_FIELD_VAR) |
||||
self.params = dict(request.GET.items()) |
||||
if PAGE_VAR in self.params: |
||||
del self.params[PAGE_VAR] |
||||
if TO_FIELD_VAR in self.params: |
||||
del self.params[TO_FIELD_VAR] |
||||
if ERROR_FLAG in self.params: |
||||
del self.params[ERROR_FLAG] |
||||
|
||||
self.order_field, self.order_type = self.get_ordering() |
||||
self.query = request.GET.get(SEARCH_VAR, '') |
||||
self.query_set = self.get_query_set() |
||||
self.get_results(request) |
||||
self.title = (self.is_popup and ugettext('Select %s') % force_unicode(self.opts.verbose_name) or ugettext('Select %s to change') % force_unicode(self.opts.verbose_name)) |
||||
self.filter_specs, self.has_filters = self.get_filters(request) |
||||
self.pk_attname = self.lookup_opts.pk.attname |
||||
|
||||
def get_filters(self, request): |
||||
filter_specs = [] |
||||
if self.list_filter: |
||||
filter_fields = [self.lookup_opts.get_field(field_name) for field_name in self.list_filter] |
||||
for f in filter_fields: |
||||
spec = FilterSpec.create(f, request, self.params, self.model, self.model_admin) |
||||
if spec and spec.has_output(): |
||||
filter_specs.append(spec) |
||||
return filter_specs, bool(filter_specs) |
||||
|
||||
def get_query_string(self, new_params=None, remove=None): |
||||
if new_params is None: new_params = {} |
||||
if remove is None: remove = [] |
||||
p = self.params.copy() |
||||
for r in remove: |
||||
for k in p.keys(): |
||||
if k.startswith(r): |
||||
del p[k] |
||||
for k, v in new_params.items(): |
||||
if v is None: |
||||
if k in p: |
||||
del p[k] |
||||
else: |
||||
p[k] = v |
||||
return '?%s' % urlencode(p) |
||||
|
||||
def get_results(self, request): |
||||
paginator = Paginator(self.query_set, self.list_per_page) |
||||
# Get the number of objects, with admin filters applied. |
||||
try: |
||||
result_count = paginator.count |
||||
# Naked except! Because we don't have any other way of validating |
||||
# "params". They might be invalid if the keyword arguments are |
||||
# incorrect, or if the values are not in the correct type (which would |
||||
# result in a database error). |
||||
except: |
||||
raise IncorrectLookupParameters |
||||
|
||||
# Get the total number of objects, with no admin filters applied. |
||||
# Perform a slight optimization: Check to see whether any filters were |
||||
# given. If not, use paginator.hits to calculate the number of objects, |
||||
# because we've already done paginator.hits and the value is cached. |
||||
if not self.query_set.query.where: |
||||
full_result_count = result_count |
||||
else: |
||||
full_result_count = self.root_query_set.count() |
||||
|
||||
can_show_all = result_count <= MAX_SHOW_ALL_ALLOWED |
||||
multi_page = result_count > self.list_per_page |
||||
|
||||
# Get the list of objects to display on this page. |
||||
if (self.show_all and can_show_all) or not multi_page: |
||||
result_list = list(self.query_set) |
||||
else: |
||||
try: |
||||
result_list = paginator.page(self.page_num+1).object_list |
||||
except InvalidPage: |
||||
result_list = () |
||||
|
||||
self.result_count = result_count |
||||
self.full_result_count = full_result_count |
||||
self.result_list = result_list |
||||
self.can_show_all = can_show_all |
||||
self.multi_page = multi_page |
||||
self.paginator = paginator |
||||
|
||||
def get_ordering(self): |
||||
lookup_opts, params = self.lookup_opts, self.params |
||||
# For ordering, first check the "ordering" parameter in the admin |
||||
# options, then check the object's default ordering. If neither of |
||||
# those exist, order descending by ID by default. Finally, look for |
||||
# manually-specified ordering from the query string. |
||||
ordering = self.model_admin.ordering or lookup_opts.ordering or ['-' + lookup_opts.pk.name] |
||||
|
||||
if ordering[0].startswith('-'): |
||||
order_field, order_type = ordering[0][1:], 'desc' |
||||
else: |
||||
order_field, order_type = ordering[0], 'asc' |
||||
if ORDER_VAR in params: |
||||
try: |
||||
field_name = self.list_display[int(params[ORDER_VAR])] |
||||
try: |
||||
f = lookup_opts.get_field(field_name) |
||||
except models.FieldDoesNotExist: |
||||
# See whether field_name is a name of a non-field |
||||
# that allows sorting. |
||||
try: |
||||
attr = getattr(self.model, field_name) |
||||
order_field = attr.admin_order_field |
||||
except AttributeError: |
||||
pass |
||||
else: |
||||
if not isinstance(f.rel, models.ManyToOneRel) or not f.null: |
||||
order_field = f.name |
||||
except (IndexError, ValueError): |
||||
pass # Invalid ordering specified. Just use the default. |
||||
if ORDER_TYPE_VAR in params and params[ORDER_TYPE_VAR] in ('asc', 'desc'): |
||||
order_type = params[ORDER_TYPE_VAR] |
||||
return order_field, order_type |
||||
|
||||
def get_query_set(self): |
||||
qs = self.root_query_set |
||||
lookup_params = self.params.copy() # a dictionary of the query string |
||||
for i in (ALL_VAR, ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, IS_POPUP_VAR): |
||||
if i in lookup_params: |
||||
del lookup_params[i] |
||||
for key, value in lookup_params.items(): |
||||
if not isinstance(key, str): |
||||
# 'key' will be used as a keyword argument later, so Python |
||||
# requires it to be a string. |
||||
del lookup_params[key] |
||||
lookup_params[smart_str(key)] = value |
||||
|
||||
# if key ends with __in, split parameter into separate values |
||||
if key.endswith('__in'): |
||||
lookup_params[key] = value.split(',') |
||||
|
||||
# Apply lookup parameters from the query string. |
||||
qs = qs.filter(**lookup_params) |
||||
|
||||
# Use select_related() if one of the list_display options is a field |
||||
# with a relationship. |
||||
if self.list_select_related: |
||||
qs = qs.select_related() |
||||
else: |
||||
for field_name in self.list_display: |
||||
try: |
||||
f = self.lookup_opts.get_field(field_name) |
||||
except models.FieldDoesNotExist: |
||||
pass |
||||
else: |
||||
if isinstance(f.rel, models.ManyToOneRel): |
||||
qs = qs.select_related() |
||||
break |
||||
|
||||
# Set ordering. |
||||
if self.order_field: |
||||
qs = qs.order_by('%s%s' % ((self.order_type == 'desc' and '-' or ''), self.order_field)) |
||||
|
||||
# Apply keyword searches. |
||||
def construct_search(field_name): |
||||
if field_name.startswith('^'): |
||||
return "%s__istartswith" % field_name[1:] |
||||
elif field_name.startswith('='): |
||||
return "%s__iexact" % field_name[1:] |
||||
elif field_name.startswith('@'): |
||||
return "%s__search" % field_name[1:] |
||||
else: |
||||
return "%s__icontains" % field_name |
||||
|
||||
if self.search_fields and self.query: |
||||
for bit in self.query.split(): |
||||
or_queries = [models.Q(**{construct_search(field_name): bit}) for field_name in self.search_fields] |
||||
other_qs = QuerySet(self.model) |
||||
other_qs.dup_select_related(qs) |
||||
other_qs = other_qs.filter(reduce(operator.or_, or_queries)) |
||||
qs = qs & other_qs |
||||
for field_name in self.search_fields: |
||||
if '__' in field_name: |
||||
qs = qs.distinct() |
||||
break |
||||
|
||||
if self.opts.one_to_one_field: |
||||
qs = qs.complex_filter(self.opts.one_to_one_field.rel.limit_choices_to) |
||||
|
||||
return qs |
||||
|
||||
def url_for_result(self, result): |
||||
return "%s/" % quote(getattr(result, self.pk_attname)) |
||||
@ -0,0 +1,77 @@
@@ -0,0 +1,77 @@
|
||||
from django import template, forms |
||||
from django.contrib.admin.views.decorators import staff_member_required |
||||
from django.template import loader |
||||
from django.shortcuts import render_to_response |
||||
from django.contrib.sites.models import Site |
||||
from django.conf import settings |
||||
from django.utils.translation import ugettext_lazy as _ |
||||
|
||||
|
||||
def template_validator(request): |
||||
""" |
||||
Displays the template validator form, which finds and displays template |
||||
syntax errors. |
||||
""" |
||||
# get a dict of {site_id : settings_module} for the validator |
||||
settings_modules = {} |
||||
for mod in settings.ADMIN_FOR: |
||||
settings_module = __import__(mod, {}, {}, ['']) |
||||
settings_modules[settings_module.SITE_ID] = settings_module |
||||
site_list = Site.objects.in_bulk(settings_modules.keys()).values() |
||||
if request.POST: |
||||
form = TemplateValidatorForm(settings_modules, site_list, |
||||
data=request.POST) |
||||
if form.is_valid(): |
||||
request.user.message_set.create(message='The template is valid.') |
||||
else: |
||||
form = TemplateValidatorForm(settings_modules, site_list) |
||||
return render_to_response('admin/template_validator.html', { |
||||
'title': 'Template validator', |
||||
'form': form, |
||||
}, context_instance=template.RequestContext(request)) |
||||
template_validator = staff_member_required(template_validator) |
||||
|
||||
|
||||
class TemplateValidatorForm(forms.Form): |
||||
site = forms.ChoiceField(_('site')) |
||||
template = forms.CharField( |
||||
_('template'), widget=forms.Textarea({'rows': 25, 'cols': 80})) |
||||
|
||||
def __init__(self, settings_modules, site_list, *args, **kwargs): |
||||
self.settings_modules = settings_modules |
||||
super(TemplateValidatorForm, self).__init__(*args, **kwargs) |
||||
self.fields['site'].choices = [(s.id, s.name) for s in site_list] |
||||
|
||||
def clean_template(self): |
||||
# Get the settings module. If the site isn't set, we don't raise an |
||||
# error since the site field will. |
||||
try: |
||||
site_id = int(self.cleaned_data.get('site', None)) |
||||
except (ValueError, TypeError): |
||||
return |
||||
settings_module = self.settings_modules.get(site_id, None) |
||||
if settings_module is None: |
||||
return |
||||
|
||||
# So that inheritance works in the site's context, register a new |
||||
# function for "extends" that uses the site's TEMPLATE_DIRS instead. |
||||
def new_do_extends(parser, token): |
||||
node = loader.do_extends(parser, token) |
||||
node.template_dirs = settings_module.TEMPLATE_DIRS |
||||
return node |
||||
register = template.Library() |
||||
register.tag('extends', new_do_extends) |
||||
template.builtins.append(register) |
||||
|
||||
# Now validate the template using the new TEMPLATE_DIRS, making sure to |
||||
# reset the extends function in any case. |
||||
error = None |
||||
template_string = self.cleaned_data['template'] |
||||
try: |
||||
tmpl = loader.get_template_from_string(template_string) |
||||
tmpl.render(template.Context({})) |
||||
except template.TemplateSyntaxError, e: |
||||
error = e |
||||
template.builtins.remove(register) |
||||
if error: |
||||
raise forms.ValidationError, e.args |
||||
@ -0,0 +1,280 @@
@@ -0,0 +1,280 @@
|
||||
""" |
||||
Form Widget classes specific to the Django admin site. |
||||
""" |
||||
|
||||
import copy |
||||
|
||||
from django import forms |
||||
from django.forms.widgets import RadioFieldRenderer |
||||
from django.forms.util import flatatt |
||||
from django.utils.text import truncate_words |
||||
from django.utils.translation import ugettext as _ |
||||
from django.utils.safestring import mark_safe |
||||
from django.utils.encoding import force_unicode |
||||
from django.conf import settings |
||||
|
||||
class FilteredSelectMultiple(forms.SelectMultiple): |
||||
""" |
||||
A SelectMultiple with a JavaScript filter interface. |
||||
|
||||
Note that the resulting JavaScript assumes that the jsi18n |
||||
catalog has been loaded in the page |
||||
""" |
||||
class Media: |
||||
js = (settings.ADMIN_MEDIA_PREFIX + "js/core.js", |
||||
settings.ADMIN_MEDIA_PREFIX + "js/SelectBox.js", |
||||
settings.ADMIN_MEDIA_PREFIX + "js/SelectFilter2.js") |
||||
|
||||
def __init__(self, verbose_name, is_stacked, attrs=None, choices=()): |
||||
self.verbose_name = verbose_name |
||||
self.is_stacked = is_stacked |
||||
super(FilteredSelectMultiple, self).__init__(attrs, choices) |
||||
|
||||
def render(self, name, value, attrs=None, choices=()): |
||||
output = [super(FilteredSelectMultiple, self).render(name, value, attrs, choices)] |
||||
output.append(u'<script type="text/javascript">addEvent(window, "load", function(e) {') |
||||
# TODO: "id_" is hard-coded here. This should instead use the correct |
||||
# API to determine the ID dynamically. |
||||
output.append(u'SelectFilter.init("id_%s", "%s", %s, "%s"); });</script>\n' % \ |
||||
(name, self.verbose_name.replace('"', '\\"'), int(self.is_stacked), settings.ADMIN_MEDIA_PREFIX)) |
||||
return mark_safe(u''.join(output)) |
||||
|
||||
class AdminDateWidget(forms.TextInput): |
||||
class Media: |
||||
js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js", |
||||
settings.ADMIN_MEDIA_PREFIX + "js/admin/DateTimeShortcuts.js") |
||||
|
||||
def __init__(self, attrs={}): |
||||
super(AdminDateWidget, self).__init__(attrs={'class': 'vDateField', 'size': '10'}) |
||||
|
||||
class AdminTimeWidget(forms.TextInput): |
||||
class Media: |
||||
js = (settings.ADMIN_MEDIA_PREFIX + "js/calendar.js", |
||||
settings.ADMIN_MEDIA_PREFIX + "js/admin/DateTimeShortcuts.js") |
||||
|
||||
def __init__(self, attrs={}): |
||||
super(AdminTimeWidget, self).__init__(attrs={'class': 'vTimeField', 'size': '8'}) |
||||
|
||||
class AdminSplitDateTime(forms.SplitDateTimeWidget): |
||||
""" |
||||
A SplitDateTime Widget that has some admin-specific styling. |
||||
""" |
||||
def __init__(self, attrs=None): |
||||
widgets = [AdminDateWidget, AdminTimeWidget] |
||||
# Note that we're calling MultiWidget, not SplitDateTimeWidget, because |
||||
# we want to define widgets. |
||||
forms.MultiWidget.__init__(self, widgets, attrs) |
||||
|
||||
def format_output(self, rendered_widgets): |
||||
return mark_safe(u'<p class="datetime">%s %s<br />%s %s</p>' % \ |
||||
(_('Date:'), rendered_widgets[0], _('Time:'), rendered_widgets[1])) |
||||
|
||||
class AdminRadioFieldRenderer(RadioFieldRenderer): |
||||
def render(self): |
||||
"""Outputs a <ul> for this set of radio fields.""" |
||||
return mark_safe(u'<ul%s>\n%s\n</ul>' % ( |
||||
flatatt(self.attrs), |
||||
u'\n'.join([u'<li>%s</li>' % force_unicode(w) for w in self])) |
||||
) |
||||
|
||||
class AdminRadioSelect(forms.RadioSelect): |
||||
renderer = AdminRadioFieldRenderer |
||||
|
||||
class AdminFileWidget(forms.FileInput): |
||||
""" |
||||
A FileField Widget that shows its current value if it has one. |
||||
""" |
||||
def __init__(self, attrs={}): |
||||
super(AdminFileWidget, self).__init__(attrs) |
||||
|
||||
def render(self, name, value, attrs=None): |
||||
output = [] |
||||
if value and hasattr(value, "url"): |
||||
output.append('%s <a target="_blank" href="%s">%s</a> <br />%s ' % \ |
||||
(_('Currently:'), value.url, value, _('Change:'))) |
||||
output.append(super(AdminFileWidget, self).render(name, value, attrs)) |
||||
return mark_safe(u''.join(output)) |
||||
|
||||
class ForeignKeyRawIdWidget(forms.TextInput): |
||||
""" |
||||
A Widget for displaying ForeignKeys in the "raw_id" interface rather than |
||||
in a <select> box. |
||||
""" |
||||
def __init__(self, rel, attrs=None): |
||||
self.rel = rel |
||||
super(ForeignKeyRawIdWidget, self).__init__(attrs) |
||||
|
||||
def render(self, name, value, attrs=None): |
||||
if attrs is None: |
||||
attrs = {} |
||||
related_url = '../../../%s/%s/' % (self.rel.to._meta.app_label, self.rel.to._meta.object_name.lower()) |
||||
params = self.url_parameters() |
||||
if params: |
||||
url = '?' + '&'.join(['%s=%s' % (k, v) for k, v in params.items()]) |
||||
else: |
||||
url = '' |
||||
if not attrs.has_key('class'): |
||||
attrs['class'] = 'vForeignKeyRawIdAdminField' # The JavaScript looks for this hook. |
||||
output = [super(ForeignKeyRawIdWidget, self).render(name, value, attrs)] |
||||
# TODO: "id_" is hard-coded here. This should instead use the correct |
||||
# API to determine the ID dynamically. |
||||
output.append('<a href="%s%s" class="related-lookup" id="lookup_id_%s" onclick="return showRelatedObjectLookupPopup(this);"> ' % \ |
||||
(related_url, url, name)) |
||||
output.append('<img src="%simg/admin/selector-search.gif" width="16" height="16" alt="%s" /></a>' % (settings.ADMIN_MEDIA_PREFIX, _('Lookup'))) |
||||
if value: |
||||
output.append(self.label_for_value(value)) |
||||
return mark_safe(u''.join(output)) |
||||
|
||||
def base_url_parameters(self): |
||||
params = {} |
||||
if self.rel.limit_choices_to: |
||||
items = [] |
||||
for k, v in self.rel.limit_choices_to.items(): |
||||
if isinstance(v, list): |
||||
v = [str(x) for x in v] |
||||
else: |
||||
v = str(v) |
||||
items.append((k, ','.join(v))) |
||||
params.update(dict(items)) |
||||
return params |
||||
|
||||
def url_parameters(self): |
||||
from django.contrib.admin.views.main import TO_FIELD_VAR |
||||
params = self.base_url_parameters() |
||||
params.update({TO_FIELD_VAR: self.rel.get_related_field().name}) |
||||
return params |
||||
|
||||
def label_for_value(self, value): |
||||
key = self.rel.get_related_field().name |
||||
obj = self.rel.to.objects.get(**{key: value}) |
||||
return ' <strong>%s</strong>' % truncate_words(obj, 14) |
||||
|
||||
class ManyToManyRawIdWidget(ForeignKeyRawIdWidget): |
||||
""" |
||||
A Widget for displaying ManyToMany ids in the "raw_id" interface rather than |
||||
in a <select multiple> box. |
||||
""" |
||||
def __init__(self, rel, attrs=None): |
||||
super(ManyToManyRawIdWidget, self).__init__(rel, attrs) |
||||
|
||||
def render(self, name, value, attrs=None): |
||||
attrs['class'] = 'vManyToManyRawIdAdminField' |
||||
if value: |
||||
value = ','.join([str(v) for v in value]) |
||||
else: |
||||
value = '' |
||||
return super(ManyToManyRawIdWidget, self).render(name, value, attrs) |
||||
|
||||
def url_parameters(self): |
||||
return self.base_url_parameters() |
||||
|
||||
def label_for_value(self, value): |
||||
return '' |
||||
|
||||
def value_from_datadict(self, data, files, name): |
||||
value = data.get(name, None) |
||||
if value and ',' in value: |
||||
return data[name].split(',') |
||||
if value: |
||||
return [value] |
||||
return None |
||||
|
||||
def _has_changed(self, initial, data): |
||||
if initial is None: |
||||
initial = [] |
||||
if data is None: |
||||
data = [] |
||||
if len(initial) != len(data): |
||||
return True |
||||
for pk1, pk2 in zip(initial, data): |
||||
if force_unicode(pk1) != force_unicode(pk2): |
||||
return True |
||||
return False |
||||
|
||||
class RelatedFieldWidgetWrapper(forms.Widget): |
||||
""" |
||||
This class is a wrapper to a given widget to add the add icon for the |
||||
admin interface. |
||||
""" |
||||
def __init__(self, widget, rel, admin_site): |
||||
self.is_hidden = widget.is_hidden |
||||
self.needs_multipart_form = widget.needs_multipart_form |
||||
self.attrs = widget.attrs |
||||
self.choices = widget.choices |
||||
self.widget = widget |
||||
self.rel = rel |
||||
# so we can check if the related object is registered with this AdminSite |
||||
self.admin_site = admin_site |
||||
|
||||
def __deepcopy__(self, memo): |
||||
obj = copy.copy(self) |
||||
obj.widget = copy.deepcopy(self.widget, memo) |
||||
obj.attrs = self.widget.attrs |
||||
memo[id(self)] = obj |
||||
return obj |
||||
|
||||
def _media(self): |
||||
return self.widget.media |
||||
media = property(_media) |
||||
|
||||
def render(self, name, value, *args, **kwargs): |
||||
rel_to = self.rel.to |
||||
related_url = '../../../%s/%s/' % (rel_to._meta.app_label, rel_to._meta.object_name.lower()) |
||||
self.widget.choices = self.choices |
||||
output = [self.widget.render(name, value, *args, **kwargs)] |
||||
if rel_to in self.admin_site._registry: # If the related object has an admin interface: |
||||
# TODO: "id_" is hard-coded here. This should instead use the correct |
||||
# API to determine the ID dynamically. |
||||
output.append(u'<a href="%sadd/" class="add-another" id="add_id_%s" onclick="return showAddAnotherPopup(this);"> ' % \ |
||||
(related_url, name)) |
||||
output.append(u'<img src="%simg/admin/icon_addlink.gif" width="10" height="10" alt="%s"/></a>' % (settings.ADMIN_MEDIA_PREFIX, _('Add Another'))) |
||||
return mark_safe(u''.join(output)) |
||||
|
||||
def build_attrs(self, extra_attrs=None, **kwargs): |
||||
"Helper function for building an attribute dictionary." |
||||
self.attrs = self.widget.build_attrs(extra_attrs=None, **kwargs) |
||||
return self.attrs |
||||
|
||||
def value_from_datadict(self, data, files, name): |
||||
return self.widget.value_from_datadict(data, files, name) |
||||
|
||||
def _has_changed(self, initial, data): |
||||
return self.widget._has_changed(initial, data) |
||||
|
||||
def id_for_label(self, id_): |
||||
return self.widget.id_for_label(id_) |
||||
|
||||
class AdminTextareaWidget(forms.Textarea): |
||||
def __init__(self, attrs=None): |
||||
final_attrs = {'class': 'vLargeTextField'} |
||||
if attrs is not None: |
||||
final_attrs.update(attrs) |
||||
super(AdminTextareaWidget, self).__init__(attrs=final_attrs) |
||||
|
||||
class AdminTextInputWidget(forms.TextInput): |
||||
def __init__(self, attrs=None): |
||||
final_attrs = {'class': 'vTextField'} |
||||
if attrs is not None: |
||||
final_attrs.update(attrs) |
||||
super(AdminTextInputWidget, self).__init__(attrs=final_attrs) |
||||
|
||||
class AdminURLFieldWidget(forms.TextInput): |
||||
def __init__(self, attrs=None): |
||||
final_attrs = {'class': 'vURLField'} |
||||
if attrs is not None: |
||||
final_attrs.update(attrs) |
||||
super(AdminURLFieldWidget, self).__init__(attrs=final_attrs) |
||||
|
||||
class AdminIntegerFieldWidget(forms.TextInput): |
||||
def __init__(self, attrs=None): |
||||
final_attrs = {'class': 'vIntegerField'} |
||||
if attrs is not None: |
||||
final_attrs.update(attrs) |
||||
super(AdminIntegerFieldWidget, self).__init__(attrs=final_attrs) |
||||
|
||||
class AdminCommaSeparatedIntegerFieldWidget(forms.TextInput): |
||||
def __init__(self, attrs=None): |
||||
final_attrs = {'class': 'vCommaSeparatedIntegerField'} |
||||
if attrs is not None: |
||||
final_attrs.update(attrs) |
||||
super(AdminCommaSeparatedIntegerFieldWidget, self).__init__(attrs=final_attrs) |
||||
@ -0,0 +1,41 @@
@@ -0,0 +1,41 @@
|
||||
from django.conf.urls.defaults import * |
||||
from django.contrib.admindocs import views |
||||
|
||||
urlpatterns = patterns('', |
||||
url('^$', |
||||
views.doc_index, |
||||
name='django-admindocs-docroot' |
||||
), |
||||
url('^bookmarklets/$', |
||||
views.bookmarklets, |
||||
name='django-admindocs-bookmarklets' |
||||
), |
||||
url('^tags/$', |
||||
views.template_tag_index, |
||||
name='django-admindocs-tags' |
||||
), |
||||
url('^filters/$', |
||||
views.template_filter_index, |
||||
name='django-admindocs-filters' |
||||
), |
||||
url('^views/$', |
||||
views.view_index, |
||||
name='django-admindocs-views-index' |
||||
), |
||||
url('^views/(?P<view>[^/]+)/$', |
||||
views.view_detail, |
||||
name='django-admindocs-views-detail' |
||||
), |
||||
url('^models/$', |
||||
views.model_index, |
||||
name='django-admindocs-models-index' |
||||
), |
||||
url('^models/(?P<app_label>[^\.]+)\.(?P<model_name>[^/]+)/$', |
||||
views.model_detail, |
||||
name='django-admindocs-models-detail' |
||||
), |
||||
url('^templates/(?P<template>.*)/$', |
||||
views.template_detail, |
||||
name='django-admindocs-templates' |
||||
), |
||||
) |
||||
@ -0,0 +1,103 @@
@@ -0,0 +1,103 @@
|
||||
"Misc. utility functions/classes for admin documentation generator." |
||||
|
||||
import re |
||||
from email.Parser import HeaderParser |
||||
from email.Errors import HeaderParseError |
||||
from django.utils.safestring import mark_safe |
||||
try: |
||||
import docutils.core |
||||
import docutils.nodes |
||||
import docutils.parsers.rst.roles |
||||
except ImportError: |
||||
docutils_is_available = False |
||||
else: |
||||
docutils_is_available = True |
||||
|
||||
def trim_docstring(docstring): |
||||
""" |
||||
Uniformly trims leading/trailing whitespace from docstrings. |
||||
|
||||
Based on http://www.python.org/peps/pep-0257.html#handling-docstring-indentation |
||||
""" |
||||
if not docstring or not docstring.strip(): |
||||
return '' |
||||
# Convert tabs to spaces and split into lines |
||||
lines = docstring.expandtabs().splitlines() |
||||
indent = min([len(line) - len(line.lstrip()) for line in lines if line.lstrip()]) |
||||
trimmed = [lines[0].lstrip()] + [line[indent:].rstrip() for line in lines[1:]] |
||||
return "\n".join(trimmed).strip() |
||||
|
||||
def parse_docstring(docstring): |
||||
""" |
||||
Parse out the parts of a docstring. Returns (title, body, metadata). |
||||
""" |
||||
docstring = trim_docstring(docstring) |
||||
parts = re.split(r'\n{2,}', docstring) |
||||
title = parts[0] |
||||
if len(parts) == 1: |
||||
body = '' |
||||
metadata = {} |
||||
else: |
||||
parser = HeaderParser() |
||||
try: |
||||
metadata = parser.parsestr(parts[-1]) |
||||
except HeaderParseError: |
||||
metadata = {} |
||||
body = "\n\n".join(parts[1:]) |
||||
else: |
||||
metadata = dict(metadata.items()) |
||||
if metadata: |
||||
body = "\n\n".join(parts[1:-1]) |
||||
else: |
||||
body = "\n\n".join(parts[1:]) |
||||
return title, body, metadata |
||||
|
||||
def parse_rst(text, default_reference_context, thing_being_parsed=None, link_base='../..'): |
||||
""" |
||||
Convert the string from reST to an XHTML fragment. |
||||
""" |
||||
overrides = { |
||||
'doctitle_xform' : True, |
||||
'inital_header_level' : 3, |
||||
"default_reference_context" : default_reference_context, |
||||
"link_base" : link_base, |
||||
} |
||||
if thing_being_parsed: |
||||
thing_being_parsed = "<%s>" % thing_being_parsed |
||||
parts = docutils.core.publish_parts(text, source_path=thing_being_parsed, |
||||
destination_path=None, writer_name='html', |
||||
settings_overrides=overrides) |
||||
return mark_safe(parts['fragment']) |
||||
|
||||
# |
||||
# reST roles |
||||
# |
||||
ROLES = { |
||||
'model' : '%s/models/%s/', |
||||
'view' : '%s/views/%s/', |
||||
'template' : '%s/templates/%s/', |
||||
'filter' : '%s/filters/#%s', |
||||
'tag' : '%s/tags/#%s', |
||||
} |
||||
|
||||
def create_reference_role(rolename, urlbase): |
||||
def _role(name, rawtext, text, lineno, inliner, options=None, content=None): |
||||
if options is None: options = {} |
||||
if content is None: content = [] |
||||
node = docutils.nodes.reference(rawtext, text, refuri=(urlbase % (inliner.document.settings.link_base, text.lower())), **options) |
||||
return [node], [] |
||||
docutils.parsers.rst.roles.register_canonical_role(rolename, _role) |
||||
|
||||
def default_reference_role(name, rawtext, text, lineno, inliner, options=None, content=None): |
||||
if options is None: options = {} |
||||
if content is None: content = [] |
||||
context = inliner.document.settings.default_reference_context |
||||
node = docutils.nodes.reference(rawtext, text, refuri=(ROLES[context] % (inliner.document.settings.link_base, text.lower())), **options) |
||||
return [node], [] |
||||
|
||||
if docutils_is_available: |
||||
docutils.parsers.rst.roles.register_canonical_role('cmsreference', default_reference_role) |
||||
docutils.parsers.rst.roles.DEFAULT_INTERPRETED_ROLE = 'cmsreference' |
||||
|
||||
for name, urlbase in ROLES.items(): |
||||
create_reference_role(name, urlbase) |
||||
@ -0,0 +1,389 @@
@@ -0,0 +1,389 @@
|
||||
from django import template, templatetags |
||||
from django.template import RequestContext |
||||
from django.conf import settings |
||||
from django.contrib.admin.views.decorators import staff_member_required |
||||
from django.db import models |
||||
from django.shortcuts import render_to_response |
||||
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist |
||||
from django.http import Http404 |
||||
from django.core import urlresolvers |
||||
from django.contrib.admindocs import utils |
||||
from django.contrib.sites.models import Site |
||||
from django.utils.translation import ugettext as _ |
||||
from django.utils.safestring import mark_safe |
||||
import inspect, os, re |
||||
|
||||
# Exclude methods starting with these strings from documentation |
||||
MODEL_METHODS_EXCLUDE = ('_', 'add_', 'delete', 'save', 'set_') |
||||
|
||||
class GenericSite(object): |
||||
domain = 'example.com' |
||||
name = 'my site' |
||||
|
||||
def get_root_path(): |
||||
from django.contrib import admin |
||||
try: |
||||
return urlresolvers.reverse(admin.site.root, args=['']) |
||||
except urlresolvers.NoReverseMatch: |
||||
return getattr(settings, "ADMIN_SITE_ROOT_URL", "/admin/") |
||||
|
||||
def doc_index(request): |
||||
if not utils.docutils_is_available: |
||||
return missing_docutils_page(request) |
||||
return render_to_response('admin_doc/index.html', { |
||||
'root_path': get_root_path(), |
||||
}, context_instance=RequestContext(request)) |
||||
doc_index = staff_member_required(doc_index) |
||||
|
||||
def bookmarklets(request): |
||||
admin_root = get_root_path() |
||||
return render_to_response('admin_doc/bookmarklets.html', { |
||||
'root_path': admin_root, |
||||
'admin_url': mark_safe("%s://%s%s" % (request.is_secure() and 'https' or 'http', request.get_host(), admin_root)), |
||||
}, context_instance=RequestContext(request)) |
||||
bookmarklets = staff_member_required(bookmarklets) |
||||
|
||||
def template_tag_index(request): |
||||
if not utils.docutils_is_available: |
||||
return missing_docutils_page(request) |
||||
|
||||
load_all_installed_template_libraries() |
||||
|
||||
tags = [] |
||||
for module_name, library in template.libraries.items(): |
||||
for tag_name, tag_func in library.tags.items(): |
||||
title, body, metadata = utils.parse_docstring(tag_func.__doc__) |
||||
if title: |
||||
title = utils.parse_rst(title, 'tag', _('tag:') + tag_name) |
||||
if body: |
||||
body = utils.parse_rst(body, 'tag', _('tag:') + tag_name) |
||||
for key in metadata: |
||||
metadata[key] = utils.parse_rst(metadata[key], 'tag', _('tag:') + tag_name) |
||||
if library in template.builtins: |
||||
tag_library = None |
||||
else: |
||||
tag_library = module_name.split('.')[-1] |
||||
tags.append({ |
||||
'name': tag_name, |
||||
'title': title, |
||||
'body': body, |
||||
'meta': metadata, |
||||
'library': tag_library, |
||||
}) |
||||
return render_to_response('admin_doc/template_tag_index.html', { |
||||
'root_path': get_root_path(), |
||||
'tags': tags |
||||
}, context_instance=RequestContext(request)) |
||||
template_tag_index = staff_member_required(template_tag_index) |
||||
|
||||
def template_filter_index(request): |
||||
if not utils.docutils_is_available: |
||||
return missing_docutils_page(request) |
||||
|
||||
load_all_installed_template_libraries() |
||||
|
||||
filters = [] |
||||
for module_name, library in template.libraries.items(): |
||||
for filter_name, filter_func in library.filters.items(): |
||||
title, body, metadata = utils.parse_docstring(filter_func.__doc__) |
||||
if title: |
||||
title = utils.parse_rst(title, 'filter', _('filter:') + filter_name) |
||||
if body: |
||||
body = utils.parse_rst(body, 'filter', _('filter:') + filter_name) |
||||
for key in metadata: |
||||
metadata[key] = utils.parse_rst(metadata[key], 'filter', _('filter:') + filter_name) |
||||
if library in template.builtins: |
||||
tag_library = None |
||||
else: |
||||
tag_library = module_name.split('.')[-1] |
||||
filters.append({ |
||||
'name': filter_name, |
||||
'title': title, |
||||
'body': body, |
||||
'meta': metadata, |
||||
'library': tag_library, |
||||
}) |
||||
return render_to_response('admin_doc/template_filter_index.html', { |
||||
'root_path': get_root_path(), |
||||
'filters': filters |
||||
}, context_instance=RequestContext(request)) |
||||
template_filter_index = staff_member_required(template_filter_index) |
||||
|
||||
def view_index(request): |
||||
if not utils.docutils_is_available: |
||||
return missing_docutils_page(request) |
||||
|
||||
if settings.ADMIN_FOR: |
||||
settings_modules = [__import__(m, {}, {}, ['']) for m in settings.ADMIN_FOR] |
||||
else: |
||||
settings_modules = [settings] |
||||
|
||||
views = [] |
||||
for settings_mod in settings_modules: |
||||
urlconf = __import__(settings_mod.ROOT_URLCONF, {}, {}, ['']) |
||||
view_functions = extract_views_from_urlpatterns(urlconf.urlpatterns) |
||||
if Site._meta.installed: |
||||
site_obj = Site.objects.get(pk=settings_mod.SITE_ID) |
||||
else: |
||||
site_obj = GenericSite() |
||||
for (func, regex) in view_functions: |
||||
views.append({ |
||||
'name': func.__name__, |
||||
'module': func.__module__, |
||||
'site_id': settings_mod.SITE_ID, |
||||
'site': site_obj, |
||||
'url': simplify_regex(regex), |
||||
}) |
||||
return render_to_response('admin_doc/view_index.html', { |
||||
'root_path': get_root_path(), |
||||
'views': views |
||||
}, context_instance=RequestContext(request)) |
||||
view_index = staff_member_required(view_index) |
||||
|
||||
def view_detail(request, view): |
||||
if not utils.docutils_is_available: |
||||
return missing_docutils_page(request) |
||||
|
||||
mod, func = urlresolvers.get_mod_func(view) |
||||
try: |
||||
view_func = getattr(__import__(mod, {}, {}, ['']), func) |
||||
except (ImportError, AttributeError): |
||||
raise Http404 |
||||
title, body, metadata = utils.parse_docstring(view_func.__doc__) |
||||
if title: |
||||
title = utils.parse_rst(title, 'view', _('view:') + view) |
||||
if body: |
||||
body = utils.parse_rst(body, 'view', _('view:') + view) |
||||
for key in metadata: |
||||
metadata[key] = utils.parse_rst(metadata[key], 'model', _('view:') + view) |
||||
return render_to_response('admin_doc/view_detail.html', { |
||||
'root_path': get_root_path(), |
||||
'name': view, |
||||
'summary': title, |
||||
'body': body, |
||||
'meta': metadata, |
||||
}, context_instance=RequestContext(request)) |
||||
view_detail = staff_member_required(view_detail) |
||||
|
||||
def model_index(request): |
||||
if not utils.docutils_is_available: |
||||
return missing_docutils_page(request) |
||||
m_list = [m._meta for m in models.get_models()] |
||||
return render_to_response('admin_doc/model_index.html', { |
||||
'root_path': get_root_path(), |
||||
'models': m_list |
||||
}, context_instance=RequestContext(request)) |
||||
model_index = staff_member_required(model_index) |
||||
|
||||
def model_detail(request, app_label, model_name): |
||||
if not utils.docutils_is_available: |
||||
return missing_docutils_page(request) |
||||
|
||||
# Get the model class. |
||||
try: |
||||
app_mod = models.get_app(app_label) |
||||
except ImproperlyConfigured: |
||||
raise Http404, _("App %r not found") % app_label |
||||
model = None |
||||
for m in models.get_models(app_mod): |
||||
if m._meta.object_name.lower() == model_name: |
||||
model = m |
||||
break |
||||
if model is None: |
||||
raise Http404, _("Model %(model_name)r not found in app %(app_label)r") % {'model_name': model_name, 'app_label': app_label} |
||||
|
||||
opts = model._meta |
||||
|
||||
# Gather fields/field descriptions. |
||||
fields = [] |
||||
for field in opts.fields: |
||||
# ForeignKey is a special case since the field will actually be a |
||||
# descriptor that returns the other object |
||||
if isinstance(field, models.ForeignKey): |
||||
data_type = related_object_name = field.rel.to.__name__ |
||||
app_label = field.rel.to._meta.app_label |
||||
verbose = utils.parse_rst((_("the related `%(app_label)s.%(data_type)s` object") % {'app_label': app_label, 'data_type': data_type}), 'model', _('model:') + data_type) |
||||
else: |
||||
data_type = get_readable_field_data_type(field) |
||||
verbose = field.verbose_name |
||||
fields.append({ |
||||
'name': field.name, |
||||
'data_type': data_type, |
||||
'verbose': verbose, |
||||
'help_text': field.help_text, |
||||
}) |
||||
|
||||
# Gather model methods. |
||||
for func_name, func in model.__dict__.items(): |
||||
if (inspect.isfunction(func) and len(inspect.getargspec(func)[0]) == 1): |
||||
try: |
||||
for exclude in MODEL_METHODS_EXCLUDE: |
||||
if func_name.startswith(exclude): |
||||
raise StopIteration |
||||
except StopIteration: |
||||
continue |
||||
verbose = func.__doc__ |
||||
if verbose: |
||||
verbose = utils.parse_rst(utils.trim_docstring(verbose), 'model', _('model:') + opts.module_name) |
||||
fields.append({ |
||||
'name': func_name, |
||||
'data_type': get_return_data_type(func_name), |
||||
'verbose': verbose, |
||||
}) |
||||
|
||||
# Gather related objects |
||||
for rel in opts.get_all_related_objects(): |
||||
verbose = _("related `%(app_label)s.%(object_name)s` objects") % {'app_label': rel.opts.app_label, 'object_name': rel.opts.object_name} |
||||
accessor = rel.get_accessor_name() |
||||
fields.append({ |
||||
'name' : "%s.all" % accessor, |
||||
'data_type' : 'List', |
||||
'verbose' : utils.parse_rst(_("all %s") % verbose , 'model', _('model:') + opts.module_name), |
||||
}) |
||||
fields.append({ |
||||
'name' : "%s.count" % accessor, |
||||
'data_type' : 'Integer', |
||||
'verbose' : utils.parse_rst(_("number of %s") % verbose , 'model', _('model:') + opts.module_name), |
||||
}) |
||||
return render_to_response('admin_doc/model_detail.html', { |
||||
'root_path': get_root_path(), |
||||
'name': '%s.%s' % (opts.app_label, opts.object_name), |
||||
'summary': _("Fields on %s objects") % opts.object_name, |
||||
'description': model.__doc__, |
||||
'fields': fields, |
||||
}, context_instance=RequestContext(request)) |
||||
model_detail = staff_member_required(model_detail) |
||||
|
||||
def template_detail(request, template): |
||||
templates = [] |
||||
for site_settings_module in settings.ADMIN_FOR: |
||||
settings_mod = __import__(site_settings_module, {}, {}, ['']) |
||||
if Site._meta.installed: |
||||
site_obj = Site.objects.get(pk=settings_mod.SITE_ID) |
||||
else: |
||||
site_obj = GenericSite() |
||||
for dir in settings_mod.TEMPLATE_DIRS: |
||||
template_file = os.path.join(dir, "%s.html" % template) |
||||
templates.append({ |
||||
'file': template_file, |
||||
'exists': os.path.exists(template_file), |
||||
'contents': lambda: os.path.exists(template_file) and open(template_file).read() or '', |
||||
'site_id': settings_mod.SITE_ID, |
||||
'site': site_obj, |
||||
'order': list(settings_mod.TEMPLATE_DIRS).index(dir), |
||||
}) |
||||
return render_to_response('admin_doc/template_detail.html', { |
||||
'root_path': get_root_path(), |
||||
'name': template, |
||||
'templates': templates, |
||||
}, context_instance=RequestContext(request)) |
||||
template_detail = staff_member_required(template_detail) |
||||
|
||||
#################### |
||||
# Helper functions # |
||||
#################### |
||||
|
||||
def missing_docutils_page(request): |
||||
"""Display an error message for people without docutils""" |
||||
return render_to_response('admin_doc/missing_docutils.html') |
||||
|
||||
def load_all_installed_template_libraries(): |
||||
# Load/register all template tag libraries from installed apps. |
||||
for e in templatetags.__path__: |
||||
libraries = [os.path.splitext(p)[0] for p in os.listdir(e) if p.endswith('.py') and p[0].isalpha()] |
||||
for library_name in libraries: |
||||
try: |
||||
lib = template.get_library("django.templatetags.%s" % library_name.split('.')[-1]) |
||||
except template.InvalidTemplateLibrary: |
||||
pass |
||||
|
||||
def get_return_data_type(func_name): |
||||
"""Return a somewhat-helpful data type given a function name""" |
||||
if func_name.startswith('get_'): |
||||
if func_name.endswith('_list'): |
||||
return 'List' |
||||
elif func_name.endswith('_count'): |
||||
return 'Integer' |
||||
return '' |
||||
|
||||
# Maps Field objects to their human-readable data types, as strings. |
||||
# Column-type strings can contain format strings; they'll be interpolated |
||||
# against the values of Field.__dict__ before being output. |
||||
# If a column type is set to None, it won't be included in the output. |
||||
DATA_TYPE_MAPPING = { |
||||
'AutoField' : _('Integer'), |
||||
'BooleanField' : _('Boolean (Either True or False)'), |
||||
'CharField' : _('String (up to %(max_length)s)'), |
||||
'CommaSeparatedIntegerField': _('Comma-separated integers'), |
||||
'DateField' : _('Date (without time)'), |
||||
'DateTimeField' : _('Date (with time)'), |
||||
'DecimalField' : _('Decimal number'), |
||||
'EmailField' : _('E-mail address'), |
||||
'FileField' : _('File path'), |
||||
'FilePathField' : _('File path'), |
||||
'FloatField' : _('Floating point number'), |
||||
'ForeignKey' : _('Integer'), |
||||
'ImageField' : _('File path'), |
||||
'IntegerField' : _('Integer'), |
||||
'IPAddressField' : _('IP address'), |
||||
'ManyToManyField' : '', |
||||
'NullBooleanField' : _('Boolean (Either True, False or None)'), |
||||
'OneToOneField' : _('Relation to parent model'), |
||||
'PhoneNumberField' : _('Phone number'), |
||||
'PositiveIntegerField' : _('Integer'), |
||||
'PositiveSmallIntegerField' : _('Integer'), |
||||
'SlugField' : _('String (up to %(max_length)s)'), |
||||
'SmallIntegerField' : _('Integer'), |
||||
'TextField' : _('Text'), |
||||
'TimeField' : _('Time'), |
||||
'URLField' : _('URL'), |
||||
'USStateField' : _('U.S. state (two uppercase letters)'), |
||||
'XMLField' : _('XML text'), |
||||
} |
||||
|
||||
def get_readable_field_data_type(field): |
||||
return DATA_TYPE_MAPPING[field.get_internal_type()] % field.__dict__ |
||||
|
||||
def extract_views_from_urlpatterns(urlpatterns, base=''): |
||||
""" |
||||
Return a list of views from a list of urlpatterns. |
||||
|
||||
Each object in the returned list is a two-tuple: (view_func, regex) |
||||
""" |
||||
views = [] |
||||
for p in urlpatterns: |
||||
if hasattr(p, '_get_callback'): |
||||
try: |
||||
views.append((p._get_callback(), base + p.regex.pattern)) |
||||
except ViewDoesNotExist: |
||||
continue |
||||
elif hasattr(p, '_get_url_patterns'): |
||||
try: |
||||
patterns = p.url_patterns |
||||
except ImportError: |
||||
continue |
||||
views.extend(extract_views_from_urlpatterns(patterns, base + p.regex.pattern)) |
||||
else: |
||||
raise TypeError, _("%s does not appear to be a urlpattern object") % p |
||||
return views |
||||
|
||||
named_group_matcher = re.compile(r'\(\?P(<\w+>).+?\)') |
||||
non_named_group_matcher = re.compile(r'\(.*?\)') |
||||
|
||||
def simplify_regex(pattern): |
||||
""" |
||||
Clean up urlpattern regexes into something somewhat readable by Mere Humans: |
||||
turns something like "^(?P<sport_slug>\w+)/athletes/(?P<athlete_slug>\w+)/$" |
||||
into "<sport_slug>/athletes/<athlete_slug>/" |
||||
""" |
||||
# handle named groups first |
||||
pattern = named_group_matcher.sub(lambda m: m.group(1), pattern) |
||||
|
||||
# handle non-named groups |
||||
pattern = non_named_group_matcher.sub("<var>", pattern) |
||||
|
||||
# clean up any outstanding regex-y characters. |
||||
pattern = pattern.replace('^', '').replace('$', '').replace('?', '').replace('//', '/').replace('\\', '') |
||||
if not pattern.startswith('/'): |
||||
pattern = '/' + pattern |
||||
return pattern |
||||
@ -0,0 +1,89 @@
@@ -0,0 +1,89 @@
|
||||
import datetime |
||||
from django.core.exceptions import ImproperlyConfigured |
||||
|
||||
SESSION_KEY = '_auth_user_id' |
||||
BACKEND_SESSION_KEY = '_auth_user_backend' |
||||
REDIRECT_FIELD_NAME = 'next' |
||||
|
||||
def load_backend(path): |
||||
i = path.rfind('.') |
||||
module, attr = path[:i], path[i+1:] |
||||
try: |
||||
mod = __import__(module, {}, {}, [attr]) |
||||
except ImportError, e: |
||||
raise ImproperlyConfigured, 'Error importing authentication backend %s: "%s"' % (module, e) |
||||
except ValueError, e: |
||||
raise ImproperlyConfigured, 'Error importing authentication backends. Is AUTHENTICATION_BACKENDS a correctly defined list or tuple?' |
||||
try: |
||||
cls = getattr(mod, attr) |
||||
except AttributeError: |
||||
raise ImproperlyConfigured, 'Module "%s" does not define a "%s" authentication backend' % (module, attr) |
||||
return cls() |
||||
|
||||
def get_backends(): |
||||
from django.conf import settings |
||||
backends = [] |
||||
for backend_path in settings.AUTHENTICATION_BACKENDS: |
||||
backends.append(load_backend(backend_path)) |
||||
return backends |
||||
|
||||
def authenticate(**credentials): |
||||
""" |
||||
If the given credentials are valid, return a User object. |
||||
""" |
||||
for backend in get_backends(): |
||||
try: |
||||
user = backend.authenticate(**credentials) |
||||
except TypeError: |
||||
# This backend doesn't accept these credentials as arguments. Try the next one. |
||||
continue |
||||
if user is None: |
||||
continue |
||||
# Annotate the user object with the path of the backend. |
||||
user.backend = "%s.%s" % (backend.__module__, backend.__class__.__name__) |
||||
return user |
||||
|
||||
def login(request, user): |
||||
""" |
||||
Persist a user id and a backend in the request. This way a user doesn't |
||||
have to reauthenticate on every request. |
||||
""" |
||||
if user is None: |
||||
user = request.user |
||||
# TODO: It would be nice to support different login methods, like signed cookies. |
||||
user.last_login = datetime.datetime.now() |
||||
user.save() |
||||
|
||||
if SESSION_KEY in request.session: |
||||
if request.session[SESSION_KEY] != user.id: |
||||
# To avoid reusing another user's session, create a new, empty |
||||
# session if the existing session corresponds to a different |
||||
# authenticated user. |
||||
request.session.flush() |
||||
else: |
||||
request.session.cycle_key() |
||||
request.session[SESSION_KEY] = user.id |
||||
request.session[BACKEND_SESSION_KEY] = user.backend |
||||
if hasattr(request, 'user'): |
||||
request.user = user |
||||
|
||||
def logout(request): |
||||
""" |
||||
Removes the authenticated user's ID from the request and flushes their |
||||
session data. |
||||
""" |
||||
request.session.flush() |
||||
if hasattr(request, 'user'): |
||||
from django.contrib.auth.models import AnonymousUser |
||||
request.user = AnonymousUser() |
||||
|
||||
def get_user(request): |
||||
from django.contrib.auth.models import AnonymousUser |
||||
try: |
||||
user_id = request.session[SESSION_KEY] |
||||
backend_path = request.session[BACKEND_SESSION_KEY] |
||||
backend = load_backend(backend_path) |
||||
user = backend.get_user(user_id) or AnonymousUser() |
||||
except KeyError: |
||||
user = AnonymousUser() |
||||
return user |
||||
@ -0,0 +1,114 @@
@@ -0,0 +1,114 @@
|
||||
|
||||
from django.contrib.auth.models import User, Group |
||||
from django.core.exceptions import PermissionDenied |
||||
from django import template |
||||
from django.shortcuts import render_to_response, get_object_or_404 |
||||
from django.template import RequestContext |
||||
from django.utils.html import escape |
||||
from django.http import HttpResponseRedirect |
||||
from django.utils.translation import ugettext, ugettext_lazy as _ |
||||
from django.contrib.auth.forms import UserCreationForm, UserChangeForm, AdminPasswordChangeForm |
||||
from django.contrib import admin |
||||
|
||||
class GroupAdmin(admin.ModelAdmin): |
||||
search_fields = ('name',) |
||||
ordering = ('name',) |
||||
filter_horizontal = ('permissions',) |
||||
|
||||
class UserAdmin(admin.ModelAdmin): |
||||
fieldsets = ( |
||||
(None, {'fields': ('username', 'password')}), |
||||
(_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}), |
||||
(_('Permissions'), {'fields': ('is_staff', 'is_active', 'is_superuser', 'user_permissions')}), |
||||
(_('Important dates'), {'fields': ('last_login', 'date_joined')}), |
||||
(_('Groups'), {'fields': ('groups',)}), |
||||
) |
||||
form = UserChangeForm |
||||
add_form = UserCreationForm |
||||
change_password_form = AdminPasswordChangeForm |
||||
list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff') |
||||
list_filter = ('is_staff', 'is_superuser') |
||||
search_fields = ('username', 'first_name', 'last_name', 'email') |
||||
ordering = ('username',) |
||||
filter_horizontal = ('user_permissions',) |
||||
|
||||
def __call__(self, request, url): |
||||
# this should not be here, but must be due to the way __call__ routes |
||||
# in ModelAdmin. |
||||
if url is None: |
||||
return self.changelist_view(request) |
||||
if url.endswith('password'): |
||||
return self.user_change_password(request, url.split('/')[0]) |
||||
return super(UserAdmin, self).__call__(request, url) |
||||
|
||||
def add_view(self, request): |
||||
if not self.has_change_permission(request): |
||||
raise PermissionDenied |
||||
if request.method == 'POST': |
||||
form = self.add_form(request.POST) |
||||
if form.is_valid(): |
||||
new_user = form.save() |
||||
msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': 'user', 'obj': new_user} |
||||
self.log_addition(request, new_user) |
||||
if "_addanother" in request.POST: |
||||
request.user.message_set.create(message=msg) |
||||
return HttpResponseRedirect(request.path) |
||||
elif '_popup' in request.REQUEST: |
||||
return self.response_add(request, new_user) |
||||
else: |
||||
request.user.message_set.create(message=msg + ' ' + ugettext("You may edit it again below.")) |
||||
return HttpResponseRedirect('../%s/' % new_user.id) |
||||
else: |
||||
form = self.add_form() |
||||
return render_to_response('admin/auth/user/add_form.html', { |
||||
'title': _('Add user'), |
||||
'form': form, |
||||
'is_popup': '_popup' in request.REQUEST, |
||||
'add': True, |
||||
'change': False, |
||||
'has_add_permission': True, |
||||
'has_delete_permission': False, |
||||
'has_change_permission': True, |
||||
'has_file_field': False, |
||||
'has_absolute_url': False, |
||||
'auto_populated_fields': (), |
||||
'opts': self.model._meta, |
||||
'save_as': False, |
||||
'username_help_text': self.model._meta.get_field('username').help_text, |
||||
'root_path': self.admin_site.root_path, |
||||
'app_label': self.model._meta.app_label, |
||||
}, context_instance=template.RequestContext(request)) |
||||
|
||||
def user_change_password(self, request, id): |
||||
if not request.user.has_perm('auth.change_user'): |
||||
raise PermissionDenied |
||||
user = get_object_or_404(self.model, pk=id) |
||||
if request.method == 'POST': |
||||
form = self.change_password_form(user, request.POST) |
||||
if form.is_valid(): |
||||
new_user = form.save() |
||||
msg = ugettext('Password changed successfully.') |
||||
request.user.message_set.create(message=msg) |
||||
return HttpResponseRedirect('..') |
||||
else: |
||||
form = self.change_password_form(user) |
||||
return render_to_response('admin/auth/user/change_password.html', { |
||||
'title': _('Change password: %s') % escape(user.username), |
||||
'form': form, |
||||
'is_popup': '_popup' in request.REQUEST, |
||||
'add': True, |
||||
'change': False, |
||||
'has_delete_permission': False, |
||||
'has_change_permission': True, |
||||
'has_absolute_url': False, |
||||
'opts': self.model._meta, |
||||
'original': user, |
||||
'save_as': False, |
||||
'show_save': True, |
||||
'root_path': self.admin_site.root_path, |
||||
}, context_instance=RequestContext(request)) |
||||
|
||||
|
||||
admin.site.register(Group, GroupAdmin) |
||||
admin.site.register(User, UserAdmin) |
||||
|
||||
@ -0,0 +1,80 @@
@@ -0,0 +1,80 @@
|
||||
try: |
||||
set |
||||
except NameError: |
||||
from sets import Set as set # Python 2.3 fallback |
||||
|
||||
from django.db import connection |
||||
from django.contrib.auth.models import User |
||||
|
||||
|
||||
class ModelBackend(object): |
||||
""" |
||||
Authenticates against django.contrib.auth.models.User. |
||||
""" |
||||
# TODO: Model, login attribute name and password attribute name should be |
||||
# configurable. |
||||
def authenticate(self, username=None, password=None): |
||||
try: |
||||
user = User.objects.get(username=username) |
||||
if user.check_password(password): |
||||
return user |
||||
except User.DoesNotExist: |
||||
return None |
||||
|
||||
def get_group_permissions(self, user_obj): |
||||
""" |
||||
Returns a set of permission strings that this user has through his/her |
||||
groups. |
||||
""" |
||||
if not hasattr(user_obj, '_group_perm_cache'): |
||||
cursor = connection.cursor() |
||||
# The SQL below works out to the following, after DB quoting: |
||||
# cursor.execute(""" |
||||
# SELECT ct."app_label", p."codename" |
||||
# FROM "auth_permission" p, "auth_group_permissions" gp, "auth_user_groups" ug, "django_content_type" ct |
||||
# WHERE p."id" = gp."permission_id" |
||||
# AND gp."group_id" = ug."group_id" |
||||
# AND ct."id" = p."content_type_id" |
||||
# AND ug."user_id" = %s, [self.id]) |
||||
qn = connection.ops.quote_name |
||||
sql = """ |
||||
SELECT ct.%s, p.%s |
||||
FROM %s p, %s gp, %s ug, %s ct |
||||
WHERE p.%s = gp.%s |
||||
AND gp.%s = ug.%s |
||||
AND ct.%s = p.%s |
||||
AND ug.%s = %%s""" % ( |
||||
qn('app_label'), qn('codename'), |
||||
qn('auth_permission'), qn('auth_group_permissions'), |
||||
qn('auth_user_groups'), qn('django_content_type'), |
||||
qn('id'), qn('permission_id'), |
||||
qn('group_id'), qn('group_id'), |
||||
qn('id'), qn('content_type_id'), |
||||
qn('user_id'),) |
||||
cursor.execute(sql, [user_obj.id]) |
||||
user_obj._group_perm_cache = set(["%s.%s" % (row[0], row[1]) for row in cursor.fetchall()]) |
||||
return user_obj._group_perm_cache |
||||
|
||||
def get_all_permissions(self, user_obj): |
||||
if not hasattr(user_obj, '_perm_cache'): |
||||
user_obj._perm_cache = set([u"%s.%s" % (p.content_type.app_label, p.codename) for p in user_obj.user_permissions.select_related()]) |
||||
user_obj._perm_cache.update(self.get_group_permissions(user_obj)) |
||||
return user_obj._perm_cache |
||||
|
||||
def has_perm(self, user_obj, perm): |
||||
return perm in self.get_all_permissions(user_obj) |
||||
|
||||
def has_module_perms(self, user_obj, app_label): |
||||
""" |
||||
Returns True if user_obj has any permissions in the given app_label. |
||||
""" |
||||
for perm in self.get_all_permissions(user_obj): |
||||
if perm[:perm.index('.')] == app_label: |
||||
return True |
||||
return False |
||||
|
||||
def get_user(self, user_id): |
||||
try: |
||||
return User.objects.get(pk=user_id) |
||||
except User.DoesNotExist: |
||||
return None |
||||
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
""" |
||||
Create a superuser from the command line. Deprecated; use manage.py |
||||
createsuperuser instead. |
||||
""" |
||||
|
||||
if __name__ == "__main__": |
||||
from django.core.management import call_command |
||||
call_command("createsuperuser") |
||||
@ -0,0 +1,70 @@
@@ -0,0 +1,70 @@
|
||||
try: |
||||
from functools import update_wrapper |
||||
except ImportError: |
||||
from django.utils.functional import update_wrapper # Python 2.3, 2.4 fallback. |
||||
|
||||
from django.contrib.auth import REDIRECT_FIELD_NAME |
||||
from django.http import HttpResponseRedirect |
||||
from django.utils.http import urlquote |
||||
|
||||
def user_passes_test(test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME): |
||||
""" |
||||
Decorator for views that checks that the user passes the given test, |
||||
redirecting to the log-in page if necessary. The test should be a callable |
||||
that takes the user object and returns True if the user passes. |
||||
""" |
||||
def decorate(view_func): |
||||
return _CheckLogin(view_func, test_func, login_url, redirect_field_name) |
||||
return decorate |
||||
|
||||
def login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME): |
||||
""" |
||||
Decorator for views that checks that the user is logged in, redirecting |
||||
to the log-in page if necessary. |
||||
""" |
||||
actual_decorator = user_passes_test( |
||||
lambda u: u.is_authenticated(), |
||||
redirect_field_name=redirect_field_name |
||||
) |
||||
if function: |
||||
return actual_decorator(function) |
||||
return actual_decorator |
||||
|
||||
def permission_required(perm, login_url=None): |
||||
""" |
||||
Decorator for views that checks whether a user has a particular permission |
||||
enabled, redirecting to the log-in page if necessary. |
||||
""" |
||||
return user_passes_test(lambda u: u.has_perm(perm), login_url=login_url) |
||||
|
||||
class _CheckLogin(object): |
||||
""" |
||||
Class that checks that the user passes the given test, redirecting to |
||||
the log-in page if necessary. If the test is passed, the view function |
||||
is invoked. The test should be a callable that takes the user object |
||||
and returns True if the user passes. |
||||
|
||||
We use a class here so that we can define __get__. This way, when a |
||||
_CheckLogin object is used as a method decorator, the view function |
||||
is properly bound to its instance. |
||||
""" |
||||
def __init__(self, view_func, test_func, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME): |
||||
if not login_url: |
||||
from django.conf import settings |
||||
login_url = settings.LOGIN_URL |
||||
self.view_func = view_func |
||||
self.test_func = test_func |
||||
self.login_url = login_url |
||||
self.redirect_field_name = redirect_field_name |
||||
update_wrapper(self, view_func) |
||||
|
||||
def __get__(self, obj, cls=None): |
||||
view_func = self.view_func.__get__(obj, cls) |
||||
return _CheckLogin(view_func, self.test_func, self.login_url, self.redirect_field_name) |
||||
|
||||
def __call__(self, request, *args, **kwargs): |
||||
if self.test_func(request.user): |
||||
return self.view_func(request, *args, **kwargs) |
||||
path = urlquote(request.get_full_path()) |
||||
tup = self.login_url, self.redirect_field_name, path |
||||
return HttpResponseRedirect('%s?%s=%s' % tup) |
||||
@ -0,0 +1,206 @@
@@ -0,0 +1,206 @@
|
||||
from django.contrib.auth.models import User |
||||
from django.contrib.auth import authenticate |
||||
from django.contrib.auth.tokens import default_token_generator |
||||
from django.contrib.sites.models import Site |
||||
from django.template import Context, loader |
||||
from django import forms |
||||
from django.utils.translation import ugettext_lazy as _ |
||||
from django.utils.http import int_to_base36 |
||||
|
||||
class UserCreationForm(forms.ModelForm): |
||||
""" |
||||
A form that creates a user, with no privileges, from the given username and password. |
||||
""" |
||||
username = forms.RegexField(label=_("Username"), max_length=30, regex=r'^\w+$', |
||||
help_text = _("Required. 30 characters or fewer. Alphanumeric characters only (letters, digits and underscores)."), |
||||
error_message = _("This value must contain only letters, numbers and underscores.")) |
||||
password1 = forms.CharField(label=_("Password"), widget=forms.PasswordInput) |
||||
password2 = forms.CharField(label=_("Password confirmation"), widget=forms.PasswordInput) |
||||
|
||||
class Meta: |
||||
model = User |
||||
fields = ("username",) |
||||
|
||||
def clean_username(self): |
||||
username = self.cleaned_data["username"] |
||||
try: |
||||
User.objects.get(username=username) |
||||
except User.DoesNotExist: |
||||
return username |
||||
raise forms.ValidationError(_("A user with that username already exists.")) |
||||
|
||||
def clean_password2(self): |
||||
password1 = self.cleaned_data.get("password1", "") |
||||
password2 = self.cleaned_data["password2"] |
||||
if password1 != password2: |
||||
raise forms.ValidationError(_("The two password fields didn't match.")) |
||||
return password2 |
||||
|
||||
def save(self, commit=True): |
||||
user = super(UserCreationForm, self).save(commit=False) |
||||
user.set_password(self.cleaned_data["password1"]) |
||||
if commit: |
||||
user.save() |
||||
return user |
||||
|
||||
class UserChangeForm(forms.ModelForm): |
||||
username = forms.RegexField(label=_("Username"), max_length=30, regex=r'^\w+$', |
||||
help_text = _("Required. 30 characters or fewer. Alphanumeric characters only (letters, digits and underscores)."), |
||||
error_message = _("This value must contain only letters, numbers and underscores.")) |
||||
|
||||
class Meta: |
||||
model = User |
||||
|
||||
class AuthenticationForm(forms.Form): |
||||
""" |
||||
Base class for authenticating users. Extend this to get a form that accepts |
||||
username/password logins. |
||||
""" |
||||
username = forms.CharField(label=_("Username"), max_length=30) |
||||
password = forms.CharField(label=_("Password"), widget=forms.PasswordInput) |
||||
|
||||
def __init__(self, request=None, *args, **kwargs): |
||||
""" |
||||
If request is passed in, the form will validate that cookies are |
||||
enabled. Note that the request (a HttpRequest object) must have set a |
||||
cookie with the key TEST_COOKIE_NAME and value TEST_COOKIE_VALUE before |
||||
running this validation. |
||||
""" |
||||
self.request = request |
||||
self.user_cache = None |
||||
super(AuthenticationForm, self).__init__(*args, **kwargs) |
||||
|
||||
def clean(self): |
||||
username = self.cleaned_data.get('username') |
||||
password = self.cleaned_data.get('password') |
||||
|
||||
if username and password: |
||||
self.user_cache = authenticate(username=username, password=password) |
||||
if self.user_cache is None: |
||||
raise forms.ValidationError(_("Please enter a correct username and password. Note that both fields are case-sensitive.")) |
||||
elif not self.user_cache.is_active: |
||||
raise forms.ValidationError(_("This account is inactive.")) |
||||
|
||||
# TODO: determine whether this should move to its own method. |
||||
if self.request: |
||||
if not self.request.session.test_cookie_worked(): |
||||
raise forms.ValidationError(_("Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in.")) |
||||
|
||||
return self.cleaned_data |
||||
|
||||
def get_user_id(self): |
||||
if self.user_cache: |
||||
return self.user_cache.id |
||||
return None |
||||
|
||||
def get_user(self): |
||||
return self.user_cache |
||||
|
||||
class PasswordResetForm(forms.Form): |
||||
email = forms.EmailField(label=_("E-mail"), max_length=75) |
||||
|
||||
def clean_email(self): |
||||
""" |
||||
Validates that a user exists with the given e-mail address. |
||||
""" |
||||
email = self.cleaned_data["email"] |
||||
self.users_cache = User.objects.filter(email__iexact=email) |
||||
if len(self.users_cache) == 0: |
||||
raise forms.ValidationError(_("That e-mail address doesn't have an associated user account. Are you sure you've registered?")) |
||||
|
||||
def save(self, domain_override=None, email_template_name='registration/password_reset_email.html', |
||||
use_https=False, token_generator=default_token_generator): |
||||
""" |
||||
Generates a one-use only link for resetting password and sends to the user |
||||
""" |
||||
from django.core.mail import send_mail |
||||
for user in self.users_cache: |
||||
if not domain_override: |
||||
current_site = Site.objects.get_current() |
||||
site_name = current_site.name |
||||
domain = current_site.domain |
||||
else: |
||||
site_name = domain = domain_override |
||||
t = loader.get_template(email_template_name) |
||||
c = { |
||||
'email': user.email, |
||||
'domain': domain, |
||||
'site_name': site_name, |
||||
'uid': int_to_base36(user.id), |
||||
'user': user, |
||||
'token': token_generator.make_token(user), |
||||
'protocol': use_https and 'https' or 'http', |
||||
} |
||||
send_mail(_("Password reset on %s") % site_name, |
||||
t.render(Context(c)), None, [user.email]) |
||||
|
||||
class SetPasswordForm(forms.Form): |
||||
""" |
||||
A form that lets a user change set his/her password without |
||||
entering the old password |
||||
""" |
||||
new_password1 = forms.CharField(label=_("New password"), widget=forms.PasswordInput) |
||||
new_password2 = forms.CharField(label=_("New password confirmation"), widget=forms.PasswordInput) |
||||
|
||||
def __init__(self, user, *args, **kwargs): |
||||
self.user = user |
||||
super(SetPasswordForm, self).__init__(*args, **kwargs) |
||||
|
||||
def clean_new_password2(self): |
||||
password1 = self.cleaned_data.get('new_password1') |
||||
password2 = self.cleaned_data.get('new_password2') |
||||
if password1 and password2: |
||||
if password1 != password2: |
||||
raise forms.ValidationError(_("The two password fields didn't match.")) |
||||
return password2 |
||||
|
||||
def save(self, commit=True): |
||||
self.user.set_password(self.cleaned_data['new_password1']) |
||||
if commit: |
||||
self.user.save() |
||||
return self.user |
||||
|
||||
class PasswordChangeForm(SetPasswordForm): |
||||
""" |
||||
A form that lets a user change his/her password by entering |
||||
their old password. |
||||
""" |
||||
old_password = forms.CharField(label=_("Old password"), widget=forms.PasswordInput) |
||||
|
||||
def clean_old_password(self): |
||||
""" |
||||
Validates that the old_password field is correct. |
||||
""" |
||||
old_password = self.cleaned_data["old_password"] |
||||
if not self.user.check_password(old_password): |
||||
raise forms.ValidationError(_("Your old password was entered incorrectly. Please enter it again.")) |
||||
return old_password |
||||
PasswordChangeForm.base_fields.keyOrder = ['old_password', 'new_password1', 'new_password2'] |
||||
|
||||
class AdminPasswordChangeForm(forms.Form): |
||||
""" |
||||
A form used to change the password of a user in the admin interface. |
||||
""" |
||||
password1 = forms.CharField(label=_("Password"), widget=forms.PasswordInput) |
||||
password2 = forms.CharField(label=_("Password (again)"), widget=forms.PasswordInput) |
||||
|
||||
def __init__(self, user, *args, **kwargs): |
||||
self.user = user |
||||
super(AdminPasswordChangeForm, self).__init__(*args, **kwargs) |
||||
|
||||
def clean_password2(self): |
||||
password1 = self.cleaned_data.get('password1') |
||||
password2 = self.cleaned_data.get('password2') |
||||
if password1 and password2: |
||||
if password1 != password2: |
||||
raise forms.ValidationError(_("The two password fields didn't match.")) |
||||
return password2 |
||||
|
||||
def save(self, commit=True): |
||||
""" |
||||
Saves the new password. |
||||
""" |
||||
self.user.set_password(self.cleaned_data["password1"]) |
||||
if commit: |
||||
self.user.save() |
||||
return self.user |
||||
@ -0,0 +1,56 @@
@@ -0,0 +1,56 @@
|
||||
from mod_python import apache |
||||
import os |
||||
|
||||
def authenhandler(req, **kwargs): |
||||
""" |
||||
Authentication handler that checks against Django's auth database. |
||||
""" |
||||
|
||||
# mod_python fakes the environ, and thus doesn't process SetEnv. This fixes |
||||
# that so that the following import works |
||||
os.environ.update(req.subprocess_env) |
||||
|
||||
# apache 2.2 requires a call to req.get_basic_auth_pw() before |
||||
# req.user and friends are available. |
||||
req.get_basic_auth_pw() |
||||
|
||||
# check for PythonOptions |
||||
_str_to_bool = lambda s: s.lower() in ('1', 'true', 'on', 'yes') |
||||
|
||||
options = req.get_options() |
||||
permission_name = options.get('DjangoPermissionName', None) |
||||
staff_only = _str_to_bool(options.get('DjangoRequireStaffStatus', "on")) |
||||
superuser_only = _str_to_bool(options.get('DjangoRequireSuperuserStatus', "off")) |
||||
settings_module = options.get('DJANGO_SETTINGS_MODULE', None) |
||||
if settings_module: |
||||
os.environ['DJANGO_SETTINGS_MODULE'] = settings_module |
||||
|
||||
from django.contrib.auth.models import User |
||||
from django import db |
||||
db.reset_queries() |
||||
|
||||
# check that the username is valid |
||||
kwargs = {'username': req.user, 'is_active': True} |
||||
if staff_only: |
||||
kwargs['is_staff'] = True |
||||
if superuser_only: |
||||
kwargs['is_superuser'] = True |
||||
try: |
||||
try: |
||||
user = User.objects.get(**kwargs) |
||||
except User.DoesNotExist: |
||||
return apache.HTTP_UNAUTHORIZED |
||||
|
||||
# check the password and any permission given |
||||
if user.check_password(req.get_basic_auth_pw()): |
||||
if permission_name: |
||||
if user.has_perm(permission_name): |
||||
return apache.OK |
||||
else: |
||||
return apache.HTTP_UNAUTHORIZED |
||||
else: |
||||
return apache.OK |
||||
else: |
||||
return apache.HTTP_UNAUTHORIZED |
||||
finally: |
||||
db.connection.close() |
||||
@ -0,0 +1,50 @@
@@ -0,0 +1,50 @@
|
||||
""" |
||||
Creates permissions for all installed apps that need permissions. |
||||
""" |
||||
|
||||
from django.db.models import get_models, signals |
||||
from django.contrib.auth import models as auth_app |
||||
|
||||
def _get_permission_codename(action, opts): |
||||
return u'%s_%s' % (action, opts.object_name.lower()) |
||||
|
||||
def _get_all_permissions(opts): |
||||
"Returns (codename, name) for all permissions in the given opts." |
||||
perms = [] |
||||
for action in ('add', 'change', 'delete'): |
||||
perms.append((_get_permission_codename(action, opts), u'Can %s %s' % (action, opts.verbose_name_raw))) |
||||
return perms + list(opts.permissions) |
||||
|
||||
def create_permissions(app, created_models, verbosity, **kwargs): |
||||
from django.contrib.contenttypes.models import ContentType |
||||
from django.contrib.auth.models import Permission |
||||
app_models = get_models(app) |
||||
if not app_models: |
||||
return |
||||
for klass in app_models: |
||||
ctype = ContentType.objects.get_for_model(klass) |
||||
for codename, name in _get_all_permissions(klass._meta): |
||||
p, created = Permission.objects.get_or_create(codename=codename, content_type__pk=ctype.id, |
||||
defaults={'name': name, 'content_type': ctype}) |
||||
if created and verbosity >= 2: |
||||
print "Adding permission '%s'" % p |
||||
|
||||
def create_superuser(app, created_models, verbosity, **kwargs): |
||||
from django.contrib.auth.models import User |
||||
from django.core.management import call_command |
||||
if User in created_models and kwargs.get('interactive', True): |
||||
msg = "\nYou just installed Django's auth system, which means you don't have " \ |
||||
"any superusers defined.\nWould you like to create one now? (yes/no): " |
||||
confirm = raw_input(msg) |
||||
while 1: |
||||
if confirm not in ('yes', 'no'): |
||||
confirm = raw_input('Please enter either "yes" or "no": ') |
||||
continue |
||||
if confirm == 'yes': |
||||
call_command("createsuperuser", interactive=True) |
||||
break |
||||
|
||||
signals.post_syncdb.connect(create_permissions, |
||||
dispatch_uid = "django.contrib.auth.management.create_permissions") |
||||
signals.post_syncdb.connect(create_superuser, |
||||
sender=auth_app, dispatch_uid = "django.contrib.auth.management.create_superuser") |
||||
@ -0,0 +1,132 @@
@@ -0,0 +1,132 @@
|
||||
""" |
||||
Management utility to create superusers. |
||||
""" |
||||
|
||||
import getpass |
||||
import os |
||||
import re |
||||
import sys |
||||
from optparse import make_option |
||||
from django.contrib.auth.models import User |
||||
from django.core import exceptions |
||||
from django.core.management.base import BaseCommand, CommandError |
||||
from django.utils.translation import ugettext as _ |
||||
|
||||
RE_VALID_USERNAME = re.compile('\w+$') |
||||
EMAIL_RE = re.compile( |
||||
r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom |
||||
r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"' # quoted-string |
||||
r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE) # domain |
||||
|
||||
def is_valid_email(value): |
||||
if not EMAIL_RE.search(value): |
||||
raise exceptions.ValidationError(_('Enter a valid e-mail address.')) |
||||
|
||||
class Command(BaseCommand): |
||||
option_list = BaseCommand.option_list + ( |
||||
make_option('--username', dest='username', default=None, |
||||
help='Specifies the username for the superuser.'), |
||||
make_option('--email', dest='email', default=None, |
||||
help='Specifies the email address for the superuser.'), |
||||
make_option('--noinput', action='store_false', dest='interactive', default=True, |
||||
help='Tells Django to NOT prompt the user for input of any kind. ' \ |
||||
'You must use --username and --email with --noinput, and ' \ |
||||
'superusers created with --noinput will not be able to log in ' \ |
||||
'until they\'re given a valid password.'), |
||||
) |
||||
help = 'Used to create a superuser.' |
||||
|
||||
def handle(self, *args, **options): |
||||
username = options.get('username', None) |
||||
email = options.get('email', None) |
||||
interactive = options.get('interactive') |
||||
|
||||
# Do quick and dirty validation if --noinput |
||||
if not interactive: |
||||
if not username or not email: |
||||
raise CommandError("You must use --username and --email with --noinput.") |
||||
if not RE_VALID_USERNAME.match(username): |
||||
raise CommandError("Invalid username. Use only letters, digits, and underscores") |
||||
try: |
||||
is_valid_email(email) |
||||
except exceptions.ValidationError: |
||||
raise CommandError("Invalid email address.") |
||||
|
||||
password = '' |
||||
|
||||
# Try to determine the current system user's username to use as a default. |
||||
try: |
||||
import pwd |
||||
except ImportError: |
||||
default_username = '' |
||||
else: |
||||
default_username = pwd.getpwuid(os.getuid())[0].replace(' ', '').lower() |
||||
|
||||
# Determine whether the default username is taken, so we don't display |
||||
# it as an option. |
||||
if default_username: |
||||
try: |
||||
User.objects.get(username=default_username) |
||||
except User.DoesNotExist: |
||||
pass |
||||
else: |
||||
default_username = '' |
||||
|
||||
# Prompt for username/email/password. Enclose this whole thing in a |
||||
# try/except to trap for a keyboard interrupt and exit gracefully. |
||||
if interactive: |
||||
try: |
||||
|
||||
# Get a username |
||||
while 1: |
||||
if not username: |
||||
input_msg = 'Username' |
||||
if default_username: |
||||
input_msg += ' (Leave blank to use %r)' % default_username |
||||
username = raw_input(input_msg + ': ') |
||||
if default_username and username == '': |
||||
username = default_username |
||||
if not RE_VALID_USERNAME.match(username): |
||||
sys.stderr.write("Error: That username is invalid. Use only letters, digits and underscores.\n") |
||||
username = None |
||||
continue |
||||
try: |
||||
User.objects.get(username=username) |
||||
except User.DoesNotExist: |
||||
break |
||||
else: |
||||
sys.stderr.write("Error: That username is already taken.\n") |
||||
username = None |
||||
|
||||
# Get an email |
||||
while 1: |
||||
if not email: |
||||
email = raw_input('E-mail address: ') |
||||
try: |
||||
is_valid_email(email) |
||||
except exceptions.ValidationError: |
||||
sys.stderr.write("Error: That e-mail address is invalid.\n") |
||||
email = None |
||||
else: |
||||
break |
||||
|
||||
# Get a password |
||||
while 1: |
||||
if not password: |
||||
password = getpass.getpass() |
||||
password2 = getpass.getpass('Password (again): ') |
||||
if password != password2: |
||||
sys.stderr.write("Error: Your passwords didn't match.\n") |
||||
password = None |
||||
continue |
||||
if password.strip() == '': |
||||
sys.stderr.write("Error: Blank passwords aren't allowed.\n") |
||||
password = None |
||||
continue |
||||
break |
||||
except KeyboardInterrupt: |
||||
sys.stderr.write("\nOperation cancelled.\n") |
||||
sys.exit(1) |
||||
|
||||
User.objects.create_superuser(username, email, password) |
||||
print "Superuser created successfully." |
||||
@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
|
||||
class LazyUser(object): |
||||
def __get__(self, request, obj_type=None): |
||||
if not hasattr(request, '_cached_user'): |
||||
from django.contrib.auth import get_user |
||||
request._cached_user = get_user(request) |
||||
return request._cached_user |
||||
|
||||
class AuthenticationMiddleware(object): |
||||
def process_request(self, request): |
||||
assert hasattr(request, 'session'), "The Django authentication middleware requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'." |
||||
request.__class__.user = LazyUser() |
||||
return None |
||||
@ -0,0 +1,376 @@
@@ -0,0 +1,376 @@
|
||||
from django.contrib import auth |
||||
from django.core.exceptions import ImproperlyConfigured |
||||
from django.db import models |
||||
from django.db.models.manager import EmptyManager |
||||
from django.contrib.contenttypes.models import ContentType |
||||
from django.utils.encoding import smart_str |
||||
from django.utils.translation import ugettext_lazy as _ |
||||
import datetime |
||||
import urllib |
||||
|
||||
UNUSABLE_PASSWORD = '!' # This will never be a valid hash |
||||
|
||||
try: |
||||
set |
||||
except NameError: |
||||
from sets import Set as set # Python 2.3 fallback |
||||
|
||||
def get_hexdigest(algorithm, salt, raw_password): |
||||
""" |
||||
Returns a string of the hexdigest of the given plaintext password and salt |
||||
using the given algorithm ('md5', 'sha1' or 'crypt'). |
||||
""" |
||||
raw_password, salt = smart_str(raw_password), smart_str(salt) |
||||
if algorithm == 'crypt': |
||||
try: |
||||
import crypt |
||||
except ImportError: |
||||
raise ValueError('"crypt" password algorithm not supported in this environment') |
||||
return crypt.crypt(raw_password, salt) |
||||
# The rest of the supported algorithms are supported by hashlib, but |
||||
# hashlib is only available in Python 2.5. |
||||
try: |
||||
import hashlib |
||||
except ImportError: |
||||
if algorithm == 'md5': |
||||
import md5 |
||||
return md5.new(salt + raw_password).hexdigest() |
||||
elif algorithm == 'sha1': |
||||
import sha |
||||
return sha.new(salt + raw_password).hexdigest() |
||||
else: |
||||
if algorithm == 'md5': |
||||
return hashlib.md5(salt + raw_password).hexdigest() |
||||
elif algorithm == 'sha1': |
||||
return hashlib.sha1(salt + raw_password).hexdigest() |
||||
raise ValueError("Got unknown password algorithm type in password.") |
||||
|
||||
def check_password(raw_password, enc_password): |
||||
""" |
||||
Returns a boolean of whether the raw_password was correct. Handles |
||||
encryption formats behind the scenes. |
||||
""" |
||||
algo, salt, hsh = enc_password.split('$') |
||||
return hsh == get_hexdigest(algo, salt, raw_password) |
||||
|
||||
class SiteProfileNotAvailable(Exception): |
||||
pass |
||||
|
||||
class Permission(models.Model): |
||||
"""The permissions system provides a way to assign permissions to specific users and groups of users. |
||||
|
||||
The permission system is used by the Django admin site, but may also be useful in your own code. The Django admin site uses permissions as follows: |
||||
|
||||
- The "add" permission limits the user's ability to view the "add" form and add an object. |
||||
- The "change" permission limits a user's ability to view the change list, view the "change" form and change an object. |
||||
- The "delete" permission limits the ability to delete an object. |
||||
|
||||
Permissions are set globally per type of object, not per specific object instance. It is possible to say "Mary may change news stories," but it's not currently possible to say "Mary may change news stories, but only the ones she created herself" or "Mary may only change news stories that have a certain status or publication date." |
||||
|
||||
Three basic permissions -- add, change and delete -- are automatically created for each Django model. |
||||
""" |
||||
name = models.CharField(_('name'), max_length=50) |
||||
content_type = models.ForeignKey(ContentType) |
||||
codename = models.CharField(_('codename'), max_length=100) |
||||
|
||||
class Meta: |
||||
verbose_name = _('permission') |
||||
verbose_name_plural = _('permissions') |
||||
unique_together = (('content_type', 'codename'),) |
||||
ordering = ('content_type__app_label', 'codename') |
||||
|
||||
def __unicode__(self): |
||||
return u"%s | %s | %s" % ( |
||||
unicode(self.content_type.app_label), |
||||
unicode(self.content_type), |
||||
unicode(self.name)) |
||||
|
||||
class Group(models.Model): |
||||
"""Groups are a generic way of categorizing users to apply permissions, or some other label, to those users. A user can belong to any number of groups. |
||||
|
||||
A user in a group automatically has all the permissions granted to that group. For example, if the group Site editors has the permission can_edit_home_page, any user in that group will have that permission. |
||||
|
||||
Beyond permissions, groups are a convenient way to categorize users to apply some label, or extended functionality, to them. For example, you could create a group 'Special users', and you could write code that would do special things to those users -- such as giving them access to a members-only portion of your site, or sending them members-only e-mail messages. |
||||
""" |
||||
name = models.CharField(_('name'), max_length=80, unique=True) |
||||
permissions = models.ManyToManyField(Permission, verbose_name=_('permissions'), blank=True) |
||||
|
||||
class Meta: |
||||
verbose_name = _('group') |
||||
verbose_name_plural = _('groups') |
||||
|
||||
def __unicode__(self): |
||||
return self.name |
||||
|
||||
class UserManager(models.Manager): |
||||
def create_user(self, username, email, password=None): |
||||
"Creates and saves a User with the given username, e-mail and password." |
||||
now = datetime.datetime.now() |
||||
user = self.model(None, username, '', '', email.strip().lower(), 'placeholder', False, True, False, now, now) |
||||
if password: |
||||
user.set_password(password) |
||||
else: |
||||
user.set_unusable_password() |
||||
user.save() |
||||
return user |
||||
|
||||
def create_superuser(self, username, email, password): |
||||
u = self.create_user(username, email, password) |
||||
u.is_staff = True |
||||
u.is_active = True |
||||
u.is_superuser = True |
||||
u.save() |
||||
|
||||
def make_random_password(self, length=10, allowed_chars='abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'): |
||||
"Generates a random password with the given length and given allowed_chars" |
||||
# Note that default value of allowed_chars does not have "I" or letters |
||||
# that look like it -- just to avoid confusion. |
||||
from random import choice |
||||
return ''.join([choice(allowed_chars) for i in range(length)]) |
||||
|
||||
class User(models.Model): |
||||
"""Users within the Django authentication system are represented by this model. |
||||
|
||||
Username and password are required. Other fields are optional. |
||||
""" |
||||
username = models.CharField(_('username'), max_length=30, unique=True, help_text=_("Required. 30 characters or fewer. Alphanumeric characters only (letters, digits and underscores).")) |
||||
first_name = models.CharField(_('first name'), max_length=30, blank=True) |
||||
last_name = models.CharField(_('last name'), max_length=30, blank=True) |
||||
email = models.EmailField(_('e-mail address'), blank=True) |
||||
password = models.CharField(_('password'), max_length=128, help_text=_("Use '[algo]$[salt]$[hexdigest]' or use the <a href=\"password/\">change password form</a>.")) |
||||
is_staff = models.BooleanField(_('staff status'), default=False, help_text=_("Designates whether the user can log into this admin site.")) |
||||
is_active = models.BooleanField(_('active'), default=True, help_text=_("Designates whether this user should be treated as active. Unselect this instead of deleting accounts.")) |
||||
is_superuser = models.BooleanField(_('superuser status'), default=False, help_text=_("Designates that this user has all permissions without explicitly assigning them.")) |
||||
last_login = models.DateTimeField(_('last login'), default=datetime.datetime.now) |
||||
date_joined = models.DateTimeField(_('date joined'), default=datetime.datetime.now) |
||||
groups = models.ManyToManyField(Group, verbose_name=_('groups'), blank=True, |
||||
help_text=_("In addition to the permissions manually assigned, this user will also get all permissions granted to each group he/she is in.")) |
||||
user_permissions = models.ManyToManyField(Permission, verbose_name=_('user permissions'), blank=True) |
||||
objects = UserManager() |
||||
|
||||
class Meta: |
||||
verbose_name = _('user') |
||||
verbose_name_plural = _('users') |
||||
|
||||
def __unicode__(self): |
||||
return self.username |
||||
|
||||
def get_absolute_url(self): |
||||
return "/users/%s/" % urllib.quote(smart_str(self.username)) |
||||
|
||||
def is_anonymous(self): |
||||
"Always returns False. This is a way of comparing User objects to anonymous users." |
||||
return False |
||||
|
||||
def is_authenticated(self): |
||||
"""Always return True. This is a way to tell if the user has been authenticated in templates. |
||||
""" |
||||
return True |
||||
|
||||
def get_full_name(self): |
||||
"Returns the first_name plus the last_name, with a space in between." |
||||
full_name = u'%s %s' % (self.first_name, self.last_name) |
||||
return full_name.strip() |
||||
|
||||
def set_password(self, raw_password): |
||||
import random |
||||
algo = 'sha1' |
||||
salt = get_hexdigest(algo, str(random.random()), str(random.random()))[:5] |
||||
hsh = get_hexdigest(algo, salt, raw_password) |
||||
self.password = '%s$%s$%s' % (algo, salt, hsh) |
||||
|
||||
def check_password(self, raw_password): |
||||
""" |
||||
Returns a boolean of whether the raw_password was correct. Handles |
||||
encryption formats behind the scenes. |
||||
""" |
||||
# Backwards-compatibility check. Older passwords won't include the |
||||
# algorithm or salt. |
||||
if '$' not in self.password: |
||||
is_correct = (self.password == get_hexdigest('md5', '', raw_password)) |
||||
if is_correct: |
||||
# Convert the password to the new, more secure format. |
||||
self.set_password(raw_password) |
||||
self.save() |
||||
return is_correct |
||||
return check_password(raw_password, self.password) |
||||
|
||||
def set_unusable_password(self): |
||||
# Sets a value that will never be a valid hash |
||||
self.password = UNUSABLE_PASSWORD |
||||
|
||||
def has_usable_password(self): |
||||
return self.password != UNUSABLE_PASSWORD |
||||
|
||||
def get_group_permissions(self): |
||||
""" |
||||
Returns a list of permission strings that this user has through |
||||
his/her groups. This method queries all available auth backends. |
||||
""" |
||||
permissions = set() |
||||
for backend in auth.get_backends(): |
||||
if hasattr(backend, "get_group_permissions"): |
||||
permissions.update(backend.get_group_permissions(self)) |
||||
return permissions |
||||
|
||||
def get_all_permissions(self): |
||||
permissions = set() |
||||
for backend in auth.get_backends(): |
||||
if hasattr(backend, "get_all_permissions"): |
||||
permissions.update(backend.get_all_permissions(self)) |
||||
return permissions |
||||
|
||||
def has_perm(self, perm): |
||||
""" |
||||
Returns True if the user has the specified permission. This method |
||||
queries all available auth backends, but returns immediately if any |
||||
backend returns True. Thus, a user who has permission from a single |
||||
auth backend is assumed to have permission in general. |
||||
""" |
||||
# Inactive users have no permissions. |
||||
if not self.is_active: |
||||
return False |
||||
|
||||
# Superusers have all permissions. |
||||
if self.is_superuser: |
||||
return True |
||||
|
||||
# Otherwise we need to check the backends. |
||||
for backend in auth.get_backends(): |
||||
if hasattr(backend, "has_perm"): |
||||
if backend.has_perm(self, perm): |
||||
return True |
||||
return False |
||||
|
||||
def has_perms(self, perm_list): |
||||
"""Returns True if the user has each of the specified permissions.""" |
||||
for perm in perm_list: |
||||
if not self.has_perm(perm): |
||||
return False |
||||
return True |
||||
|
||||
def has_module_perms(self, app_label): |
||||
""" |
||||
Returns True if the user has any permissions in the given app |
||||
label. Uses pretty much the same logic as has_perm, above. |
||||
""" |
||||
if not self.is_active: |
||||
return False |
||||
|
||||
if self.is_superuser: |
||||
return True |
||||
|
||||
for backend in auth.get_backends(): |
||||
if hasattr(backend, "has_module_perms"): |
||||
if backend.has_module_perms(self, app_label): |
||||
return True |
||||
return False |
||||
|
||||
def get_and_delete_messages(self): |
||||
messages = [] |
||||
for m in self.message_set.all(): |
||||
messages.append(m.message) |
||||
m.delete() |
||||
return messages |
||||
|
||||
def email_user(self, subject, message, from_email=None): |
||||
"Sends an e-mail to this User." |
||||
from django.core.mail import send_mail |
||||
send_mail(subject, message, from_email, [self.email]) |
||||
|
||||
def get_profile(self): |
||||
""" |
||||
Returns site-specific profile for this user. Raises |
||||
SiteProfileNotAvailable if this site does not allow profiles. |
||||
""" |
||||
if not hasattr(self, '_profile_cache'): |
||||
from django.conf import settings |
||||
if not getattr(settings, 'AUTH_PROFILE_MODULE', False): |
||||
raise SiteProfileNotAvailable |
||||
try: |
||||
app_label, model_name = settings.AUTH_PROFILE_MODULE.split('.') |
||||
model = models.get_model(app_label, model_name) |
||||
self._profile_cache = model._default_manager.get(user__id__exact=self.id) |
||||
except (ImportError, ImproperlyConfigured): |
||||
raise SiteProfileNotAvailable |
||||
return self._profile_cache |
||||
|
||||
class Message(models.Model): |
||||
""" |
||||
The message system is a lightweight way to queue messages for given |
||||
users. A message is associated with a User instance (so it is only |
||||
applicable for registered users). There's no concept of expiration or |
||||
timestamps. Messages are created by the Django admin after successful |
||||
actions. For example, "The poll Foo was created successfully." is a |
||||
message. |
||||
""" |
||||
user = models.ForeignKey(User) |
||||
message = models.TextField(_('message')) |
||||
|
||||
def __unicode__(self): |
||||
return self.message |
||||
|
||||
class AnonymousUser(object): |
||||
id = None |
||||
username = '' |
||||
is_staff = False |
||||
is_active = False |
||||
is_superuser = False |
||||
_groups = EmptyManager() |
||||
_user_permissions = EmptyManager() |
||||
|
||||
def __init__(self): |
||||
pass |
||||
|
||||
def __unicode__(self): |
||||
return 'AnonymousUser' |
||||
|
||||
def __str__(self): |
||||
return unicode(self).encode('utf-8') |
||||
|
||||
def __eq__(self, other): |
||||
return isinstance(other, self.__class__) |
||||
|
||||
def __ne__(self, other): |
||||
return not self.__eq__(other) |
||||
|
||||
def __hash__(self): |
||||
return 1 # instances always return the same hash value |
||||
|
||||
def save(self): |
||||
raise NotImplementedError |
||||
|
||||
def delete(self): |
||||
raise NotImplementedError |
||||
|
||||
def set_password(self, raw_password): |
||||
raise NotImplementedError |
||||
|
||||
def check_password(self, raw_password): |
||||
raise NotImplementedError |
||||
|
||||
def _get_groups(self): |
||||
return self._groups |
||||
groups = property(_get_groups) |
||||
|
||||
def _get_user_permissions(self): |
||||
return self._user_permissions |
||||
user_permissions = property(_get_user_permissions) |
||||
|
||||
def has_perm(self, perm): |
||||
return False |
||||
|
||||
def has_perms(self, perm_list): |
||||
return False |
||||
|
||||
def has_module_perms(self, module): |
||||
return False |
||||
|
||||
def get_and_delete_messages(self): |
||||
return [] |
||||
|
||||
def is_anonymous(self): |
||||
return True |
||||
|
||||
def is_authenticated(self): |
||||
return False |
||||
@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
|
||||
from django.contrib.auth.tests.basic import BASIC_TESTS |
||||
from django.contrib.auth.tests.views import PasswordResetTest, ChangePasswordTest |
||||
from django.contrib.auth.tests.forms import FORM_TESTS |
||||
from django.contrib.auth.tests.tokens import TOKEN_GENERATOR_TESTS |
||||
|
||||
# The password for the fixture data users is 'password' |
||||
|
||||
__test__ = { |
||||
'BASIC_TESTS': BASIC_TESTS, |
||||
'PASSWORDRESET_TESTS': PasswordResetTest, |
||||
'FORM_TESTS': FORM_TESTS, |
||||
'TOKEN_GENERATOR_TESTS': TOKEN_GENERATOR_TESTS, |
||||
'CHANGEPASSWORD_TESTS': ChangePasswordTest, |
||||
} |
||||
@ -0,0 +1,56 @@
@@ -0,0 +1,56 @@
|
||||
|
||||
BASIC_TESTS = """ |
||||
>>> from django.contrib.auth.models import User, AnonymousUser |
||||
>>> u = User.objects.create_user('testuser', 'test@example.com', 'testpw') |
||||
>>> u.has_usable_password() |
||||
True |
||||
>>> u.check_password('bad') |
||||
False |
||||
>>> u.check_password('testpw') |
||||
True |
||||
>>> u.set_unusable_password() |
||||
>>> u.save() |
||||
>>> u.check_password('testpw') |
||||
False |
||||
>>> u.has_usable_password() |
||||
False |
||||
>>> u2 = User.objects.create_user('testuser2', 'test2@example.com') |
||||
>>> u2.has_usable_password() |
||||
False |
||||
|
||||
>>> u.is_authenticated() |
||||
True |
||||
>>> u.is_staff |
||||
False |
||||
>>> u.is_active |
||||
True |
||||
|
||||
>>> a = AnonymousUser() |
||||
>>> a.is_authenticated() |
||||
False |
||||
>>> a.is_staff |
||||
False |
||||
>>> a.is_active |
||||
False |
||||
>>> a.groups.all() |
||||
[] |
||||
>>> a.user_permissions.all() |
||||
[] |
||||
|
||||
# |
||||
# Tests for createsuperuser management command. |
||||
# It's nearly impossible to test the interactive mode -- a command test helper |
||||
# would be needed (and *awesome*) -- so just test the non-interactive mode. |
||||
# This covers most of the important validation, but not all. |
||||
# |
||||
>>> from django.core.management import call_command |
||||
|
||||
>>> call_command("createsuperuser", noinput=True, username="joe", email="joe@somewhere.org") |
||||
Superuser created successfully. |
||||
|
||||
>>> u = User.objects.get(username="joe") |
||||
>>> u.email |
||||
u'joe@somewhere.org' |
||||
>>> u.password |
||||
u'!' |
||||
""" |
||||
@ -0,0 +1,193 @@
@@ -0,0 +1,193 @@
|
||||
|
||||
FORM_TESTS = """ |
||||
>>> from django.contrib.auth.models import User |
||||
>>> from django.contrib.auth.forms import UserCreationForm, AuthenticationForm |
||||
>>> from django.contrib.auth.forms import PasswordChangeForm, SetPasswordForm |
||||
|
||||
# The user already exists. |
||||
|
||||
>>> user = User.objects.create_user("jsmith", "jsmith@example.com", "test123") |
||||
>>> data = { |
||||
... 'username': 'jsmith', |
||||
... 'password1': 'test123', |
||||
... 'password2': 'test123', |
||||
... } |
||||
>>> form = UserCreationForm(data) |
||||
>>> form.is_valid() |
||||
False |
||||
>>> form["username"].errors |
||||
[u'A user with that username already exists.'] |
||||
|
||||
# The username contains invalid data. |
||||
|
||||
>>> data = { |
||||
... 'username': 'jsmith@example.com', |
||||
... 'password1': 'test123', |
||||
... 'password2': 'test123', |
||||
... } |
||||
>>> form = UserCreationForm(data) |
||||
>>> form.is_valid() |
||||
False |
||||
>>> form["username"].errors |
||||
[u'This value must contain only letters, numbers and underscores.'] |
||||
|
||||
# The verification password is incorrect. |
||||
|
||||
>>> data = { |
||||
... 'username': 'jsmith2', |
||||
... 'password1': 'test123', |
||||
... 'password2': 'test', |
||||
... } |
||||
>>> form = UserCreationForm(data) |
||||
>>> form.is_valid() |
||||
False |
||||
>>> form["password2"].errors |
||||
[u"The two password fields didn't match."] |
||||
|
||||
# One (or both) passwords weren't given |
||||
|
||||
>>> data = {'username': 'jsmith2'} |
||||
>>> form = UserCreationForm(data) |
||||
>>> form.is_valid() |
||||
False |
||||
>>> form['password1'].errors |
||||
[u'This field is required.'] |
||||
>>> form['password2'].errors |
||||
[u'This field is required.'] |
||||
|
||||
>>> data['password2'] = 'test123' |
||||
>>> form = UserCreationForm(data) |
||||
>>> form.is_valid() |
||||
False |
||||
>>> form['password1'].errors |
||||
[u'This field is required.'] |
||||
|
||||
# The success case. |
||||
|
||||
>>> data = { |
||||
... 'username': 'jsmith2', |
||||
... 'password1': 'test123', |
||||
... 'password2': 'test123', |
||||
... } |
||||
>>> form = UserCreationForm(data) |
||||
>>> form.is_valid() |
||||
True |
||||
>>> form.save() |
||||
<User: jsmith2> |
||||
|
||||
# The user submits an invalid username. |
||||
|
||||
>>> data = { |
||||
... 'username': 'jsmith_does_not_exist', |
||||
... 'password': 'test123', |
||||
... } |
||||
|
||||
>>> form = AuthenticationForm(None, data) |
||||
>>> form.is_valid() |
||||
False |
||||
>>> form.non_field_errors() |
||||
[u'Please enter a correct username and password. Note that both fields are case-sensitive.'] |
||||
|
||||
# The user is inactive. |
||||
|
||||
>>> data = { |
||||
... 'username': 'jsmith', |
||||
... 'password': 'test123', |
||||
... } |
||||
>>> user.is_active = False |
||||
>>> user.save() |
||||
>>> form = AuthenticationForm(None, data) |
||||
>>> form.is_valid() |
||||
False |
||||
>>> form.non_field_errors() |
||||
[u'This account is inactive.'] |
||||
|
||||
>>> user.is_active = True |
||||
>>> user.save() |
||||
|
||||
# The success case |
||||
|
||||
>>> form = AuthenticationForm(None, data) |
||||
>>> form.is_valid() |
||||
True |
||||
>>> form.non_field_errors() |
||||
[] |
||||
|
||||
### SetPasswordForm: |
||||
|
||||
# The two new passwords do not match. |
||||
|
||||
>>> data = { |
||||
... 'new_password1': 'abc123', |
||||
... 'new_password2': 'abc', |
||||
... } |
||||
>>> form = SetPasswordForm(user, data) |
||||
>>> form.is_valid() |
||||
False |
||||
>>> form["new_password2"].errors |
||||
[u"The two password fields didn't match."] |
||||
|
||||
# The success case. |
||||
|
||||
>>> data = { |
||||
... 'new_password1': 'abc123', |
||||
... 'new_password2': 'abc123', |
||||
... } |
||||
>>> form = SetPasswordForm(user, data) |
||||
>>> form.is_valid() |
||||
True |
||||
|
||||
### PasswordChangeForm: |
||||
|
||||
The old password is incorrect. |
||||
|
||||
>>> data = { |
||||
... 'old_password': 'test', |
||||
... 'new_password1': 'abc123', |
||||
... 'new_password2': 'abc123', |
||||
... } |
||||
>>> form = PasswordChangeForm(user, data) |
||||
>>> form.is_valid() |
||||
False |
||||
>>> form["old_password"].errors |
||||
[u'Your old password was entered incorrectly. Please enter it again.'] |
||||
|
||||
# The two new passwords do not match. |
||||
|
||||
>>> data = { |
||||
... 'old_password': 'test123', |
||||
... 'new_password1': 'abc123', |
||||
... 'new_password2': 'abc', |
||||
... } |
||||
>>> form = PasswordChangeForm(user, data) |
||||
>>> form.is_valid() |
||||
False |
||||
>>> form["new_password2"].errors |
||||
[u"The two password fields didn't match."] |
||||
|
||||
# The success case. |
||||
|
||||
>>> data = { |
||||
... 'old_password': 'test123', |
||||
... 'new_password1': 'abc123', |
||||
... 'new_password2': 'abc123', |
||||
... } |
||||
>>> form = PasswordChangeForm(user, data) |
||||
>>> form.is_valid() |
||||
True |
||||
|
||||
# Regression test - check the order of fields: |
||||
|
||||
>>> PasswordChangeForm(user, {}).fields.keys() |
||||
['old_password', 'new_password1', 'new_password2'] |
||||
|
||||
### UserChangeForm |
||||
|
||||
>>> from django.contrib.auth.forms import UserChangeForm |
||||
>>> data = {'username': 'not valid'} |
||||
>>> form = UserChangeForm(data, instance=user) |
||||
>>> form.is_valid() |
||||
False |
||||
>>> form['username'].errors |
||||
[u'This value must contain only letters, numbers and underscores.'] |
||||
""" |
||||
@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
|
||||
TOKEN_GENERATOR_TESTS = """ |
||||
>>> from django.contrib.auth.models import User, AnonymousUser |
||||
>>> from django.contrib.auth.tokens import PasswordResetTokenGenerator |
||||
>>> from django.conf import settings |
||||
>>> u = User.objects.create_user('tokentestuser', 'test2@example.com', 'testpw') |
||||
>>> p0 = PasswordResetTokenGenerator() |
||||
>>> tk1 = p0.make_token(u) |
||||
>>> p0.check_token(u, tk1) |
||||
True |
||||
|
||||
Tests to ensure we can use the token after n days, but no greater. |
||||
Use a mocked version of PasswordResetTokenGenerator so we can change |
||||
the value of 'today' |
||||
|
||||
>>> class Mocked(PasswordResetTokenGenerator): |
||||
... def __init__(self, today): |
||||
... self._today_val = today |
||||
... def _today(self): |
||||
... return self._today_val |
||||
|
||||
>>> from datetime import date, timedelta |
||||
>>> p1 = Mocked(date.today() + timedelta(settings.PASSWORD_RESET_TIMEOUT_DAYS)) |
||||
>>> p1.check_token(u, tk1) |
||||
True |
||||
>>> p2 = Mocked(date.today() + timedelta(settings.PASSWORD_RESET_TIMEOUT_DAYS + 1)) |
||||
>>> p2.check_token(u, tk1) |
||||
False |
||||
|
||||
""" |
||||
@ -0,0 +1,164 @@
@@ -0,0 +1,164 @@
|
||||
|
||||
import os |
||||
import re |
||||
|
||||
from django.conf import settings |
||||
from django.contrib.auth.models import User |
||||
from django.test import TestCase |
||||
from django.core import mail |
||||
|
||||
class PasswordResetTest(TestCase): |
||||
fixtures = ['authtestdata.json'] |
||||
urls = 'django.contrib.auth.urls' |
||||
|
||||
def test_email_not_found(self): |
||||
"Error is raised if the provided email address isn't currently registered" |
||||
response = self.client.get('/password_reset/') |
||||
self.assertEquals(response.status_code, 200) |
||||
response = self.client.post('/password_reset/', {'email': 'not_a_real_email@email.com'}) |
||||
self.assertContains(response, "That e-mail address doesn't have an associated user account") |
||||
self.assertEquals(len(mail.outbox), 0) |
||||
|
||||
def test_email_found(self): |
||||
"Email is sent if a valid email address is provided for password reset" |
||||
response = self.client.post('/password_reset/', {'email': 'staffmember@example.com'}) |
||||
self.assertEquals(response.status_code, 302) |
||||
self.assertEquals(len(mail.outbox), 1) |
||||
self.assert_("http://" in mail.outbox[0].body) |
||||
|
||||
def _test_confirm_start(self): |
||||
# Start by creating the email |
||||
response = self.client.post('/password_reset/', {'email': 'staffmember@example.com'}) |
||||
self.assertEquals(response.status_code, 302) |
||||
self.assertEquals(len(mail.outbox), 1) |
||||
return self._read_signup_email(mail.outbox[0]) |
||||
|
||||
def _read_signup_email(self, email): |
||||
urlmatch = re.search(r"https?://[^/]*(/.*reset/\S*)", email.body) |
||||
self.assert_(urlmatch is not None, "No URL found in sent email") |
||||
return urlmatch.group(), urlmatch.groups()[0] |
||||
|
||||
def test_confirm_valid(self): |
||||
url, path = self._test_confirm_start() |
||||
response = self.client.get(path) |
||||
# redirect to a 'complete' page: |
||||
self.assertEquals(response.status_code, 200) |
||||
self.assert_("Please enter your new password" in response.content) |
||||
|
||||
def test_confirm_invalid(self): |
||||
url, path = self._test_confirm_start() |
||||
# Lets munge the token in the path, but keep the same length, |
||||
# in case the URL conf will reject a different length |
||||
path = path[:-5] + ("0"*4) + path[-1] |
||||
|
||||
response = self.client.get(path) |
||||
self.assertEquals(response.status_code, 200) |
||||
self.assert_("The password reset link was invalid" in response.content) |
||||
|
||||
def test_confirm_invalid_post(self): |
||||
# Same as test_confirm_invalid, but trying |
||||
# to do a POST instead. |
||||
url, path = self._test_confirm_start() |
||||
path = path[:-5] + ("0"*4) + path[-1] |
||||
|
||||
response = self.client.post(path, {'new_password1': 'anewpassword', |
||||
'new_password2':' anewpassword'}) |
||||
# Check the password has not been changed |
||||
u = User.objects.get(email='staffmember@example.com') |
||||
self.assert_(not u.check_password("anewpassword")) |
||||
|
||||
def test_confirm_complete(self): |
||||
url, path = self._test_confirm_start() |
||||
response = self.client.post(path, {'new_password1': 'anewpassword', |
||||
'new_password2': 'anewpassword'}) |
||||
# It redirects us to a 'complete' page: |
||||
self.assertEquals(response.status_code, 302) |
||||
# Check the password has been changed |
||||
u = User.objects.get(email='staffmember@example.com') |
||||
self.assert_(u.check_password("anewpassword")) |
||||
|
||||
# Check we can't use the link again |
||||
response = self.client.get(path) |
||||
self.assertEquals(response.status_code, 200) |
||||
self.assert_("The password reset link was invalid" in response.content) |
||||
|
||||
def test_confirm_different_passwords(self): |
||||
url, path = self._test_confirm_start() |
||||
response = self.client.post(path, {'new_password1': 'anewpassword', |
||||
'new_password2':' x'}) |
||||
self.assertEquals(response.status_code, 200) |
||||
self.assert_("The two password fields didn't match" in response.content) |
||||
|
||||
|
||||
class ChangePasswordTest(TestCase): |
||||
fixtures = ['authtestdata.json'] |
||||
urls = 'django.contrib.auth.urls' |
||||
|
||||
def setUp(self): |
||||
self.old_TEMPLATE_DIRS = settings.TEMPLATE_DIRS |
||||
settings.TEMPLATE_DIRS = ( |
||||
os.path.join( |
||||
os.path.dirname(__file__), |
||||
'templates' |
||||
) |
||||
,) |
||||
|
||||
def tearDown(self): |
||||
settings.TEMPLATE_DIRS = self.old_TEMPLATE_DIRS |
||||
|
||||
def login(self, password='password'): |
||||
response = self.client.post('/login/', { |
||||
'username': 'testclient', |
||||
'password': password |
||||
} |
||||
) |
||||
self.assertEquals(response.status_code, 302) |
||||
self.assert_(response['Location'].endswith(settings.LOGIN_REDIRECT_URL)) |
||||
|
||||
def fail_login(self, password='password'): |
||||
response = self.client.post('/login/', { |
||||
'username': 'testclient', |
||||
'password': password |
||||
} |
||||
) |
||||
self.assertEquals(response.status_code, 200) |
||||
self.assert_("Please enter a correct username and password. Note that both fields are case-sensitive." in response.content) |
||||
|
||||
def logout(self): |
||||
response = self.client.get('/logout/') |
||||
|
||||
def test_password_change_fails_with_invalid_old_password(self): |
||||
self.login() |
||||
response = self.client.post('/password_change/', { |
||||
'old_password': 'donuts', |
||||
'new_password1': 'password1', |
||||
'new_password2': 'password1', |
||||
} |
||||
) |
||||
self.assertEquals(response.status_code, 200) |
||||
self.assert_("Your old password was entered incorrectly. Please enter it again." in response.content) |
||||
|
||||
def test_password_change_fails_with_mismatched_passwords(self): |
||||
self.login() |
||||
response = self.client.post('/password_change/', { |
||||
'old_password': 'password', |
||||
'new_password1': 'password1', |
||||
'new_password2': 'donuts', |
||||
} |
||||
) |
||||
self.assertEquals(response.status_code, 200) |
||||
self.assert_("The two password fields didn't match." in response.content) |
||||
|
||||
def test_password_change_succeeds(self): |
||||
self.login() |
||||
response = self.client.post('/password_change/', { |
||||
'old_password': 'password', |
||||
'new_password1': 'password1', |
||||
'new_password2': 'password1', |
||||
} |
||||
) |
||||
self.assertEquals(response.status_code, 302) |
||||
self.assert_(response['Location'].endswith('/password_change/done/')) |
||||
self.fail_login() |
||||
self.login(password='password1') |
||||
|
||||
@ -0,0 +1,66 @@
@@ -0,0 +1,66 @@
|
||||
from datetime import date |
||||
from django.conf import settings |
||||
from django.utils.http import int_to_base36, base36_to_int |
||||
|
||||
class PasswordResetTokenGenerator(object): |
||||
""" |
||||
Stratgy object used to generate and check tokens for the password |
||||
reset mechanism. |
||||
""" |
||||
def make_token(self, user): |
||||
""" |
||||
Returns a token that can be used once to do a password reset |
||||
for the given user. |
||||
""" |
||||
return self._make_token_with_timestamp(user, self._num_days(self._today())) |
||||
|
||||
def check_token(self, user, token): |
||||
""" |
||||
Check that a password reset token is correct for a given user. |
||||
""" |
||||
# Parse the tokem |
||||
try: |
||||
ts_b36, hash = token.split("-") |
||||
except ValueError: |
||||
return False |
||||
|
||||
try: |
||||
ts = base36_to_int(ts_b36) |
||||
except ValueError: |
||||
return False |
||||
|
||||
# Check that the timestamp/uid has not been tampered with |
||||
if self._make_token_with_timestamp(user, ts) != token: |
||||
return False |
||||
|
||||
# Check the timestamp is within limit |
||||
if (self._num_days(self._today()) - ts) > settings.PASSWORD_RESET_TIMEOUT_DAYS: |
||||
return False |
||||
|
||||
return True |
||||
|
||||
def _make_token_with_timestamp(self, user, timestamp): |
||||
# timestamp is number of days since 2001-1-1. Converted to |
||||
# base 36, this gives us a 3 digit string until about 2121 |
||||
ts_b36 = int_to_base36(timestamp) |
||||
|
||||
# By hashing on the internal state of the user and using state |
||||
# that is sure to change (the password salt will change as soon as |
||||
# the password is set, at least for current Django auth, and |
||||
# last_login will also change), we produce a hash that will be |
||||
# invalid as soon as it is used. |
||||
# We limit the hash to 20 chars to keep URL short |
||||
from django.utils.hashcompat import sha_constructor |
||||
hash = sha_constructor(settings.SECRET_KEY + unicode(user.id) + |
||||
user.password + unicode(user.last_login) + |
||||
unicode(timestamp)).hexdigest()[::2] |
||||
return "%s-%s" % (ts_b36, hash) |
||||
|
||||
def _num_days(self, dt): |
||||
return (dt - date(2001,1,1)).days |
||||
|
||||
def _today(self): |
||||
# Used for mocking in tests |
||||
return date.today() |
||||
|
||||
default_token_generator = PasswordResetTokenGenerator() |
||||
@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
|
||||
# These URLs are normally mapped to /admin/urls.py. This URLs file is |
||||
# provided as a convenience to those who want to deploy these URLs elsewhere. |
||||
# This file is also used to provide a reliable view deployment for test purposes. |
||||
|
||||
from django.conf.urls.defaults import * |
||||
|
||||
urlpatterns = patterns('', |
||||
(r'^login/$', 'django.contrib.auth.views.login'), |
||||
(r'^logout/$', 'django.contrib.auth.views.logout'), |
||||
(r'^password_change/$', 'django.contrib.auth.views.password_change'), |
||||
(r'^password_change/done/$', 'django.contrib.auth.views.password_change_done'), |
||||
(r'^password_reset/$', 'django.contrib.auth.views.password_reset'), |
||||
(r'^password_reset/done/$', 'django.contrib.auth.views.password_reset_done'), |
||||
(r'^reset/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$', 'django.contrib.auth.views.password_reset_confirm'), |
||||
(r'^reset/done/$', 'django.contrib.auth.views.password_reset_complete'), |
||||
) |
||||
|
||||
@ -0,0 +1,157 @@
@@ -0,0 +1,157 @@
|
||||
from django.conf import settings |
||||
from django.contrib.auth import REDIRECT_FIELD_NAME |
||||
from django.contrib.auth.decorators import login_required |
||||
from django.contrib.auth.forms import AuthenticationForm |
||||
from django.contrib.auth.forms import PasswordResetForm, SetPasswordForm, PasswordChangeForm |
||||
from django.contrib.auth.tokens import default_token_generator |
||||
from django.core.urlresolvers import reverse |
||||
from django.shortcuts import render_to_response, get_object_or_404 |
||||
from django.contrib.sites.models import Site, RequestSite |
||||
from django.http import HttpResponseRedirect, Http404 |
||||
from django.template import RequestContext |
||||
from django.utils.http import urlquote, base36_to_int |
||||
from django.utils.translation import ugettext as _ |
||||
from django.contrib.auth.models import User |
||||
from django.views.decorators.cache import never_cache |
||||
|
||||
def login(request, template_name='registration/login.html', redirect_field_name=REDIRECT_FIELD_NAME): |
||||
"Displays the login form and handles the login action." |
||||
redirect_to = request.REQUEST.get(redirect_field_name, '') |
||||
if request.method == "POST": |
||||
form = AuthenticationForm(data=request.POST) |
||||
if form.is_valid(): |
||||
# Light security check -- make sure redirect_to isn't garbage. |
||||
if not redirect_to or '//' in redirect_to or ' ' in redirect_to: |
||||
redirect_to = settings.LOGIN_REDIRECT_URL |
||||
from django.contrib.auth import login |
||||
login(request, form.get_user()) |
||||
if request.session.test_cookie_worked(): |
||||
request.session.delete_test_cookie() |
||||
return HttpResponseRedirect(redirect_to) |
||||
else: |
||||
form = AuthenticationForm(request) |
||||
request.session.set_test_cookie() |
||||
if Site._meta.installed: |
||||
current_site = Site.objects.get_current() |
||||
else: |
||||
current_site = RequestSite(request) |
||||
return render_to_response(template_name, { |
||||
'form': form, |
||||
redirect_field_name: redirect_to, |
||||
'site_name': current_site.name, |
||||
}, context_instance=RequestContext(request)) |
||||
login = never_cache(login) |
||||
|
||||
def logout(request, next_page=None, template_name='registration/logged_out.html'): |
||||
"Logs out the user and displays 'You are logged out' message." |
||||
from django.contrib.auth import logout |
||||
logout(request) |
||||
if next_page is None: |
||||
return render_to_response(template_name, {'title': _('Logged out')}, context_instance=RequestContext(request)) |
||||
else: |
||||
# Redirect to this page until the session has been cleared. |
||||
return HttpResponseRedirect(next_page or request.path) |
||||
|
||||
def logout_then_login(request, login_url=None): |
||||
"Logs out the user if he is logged in. Then redirects to the log-in page." |
||||
if not login_url: |
||||
login_url = settings.LOGIN_URL |
||||
return logout(request, login_url) |
||||
|
||||
def redirect_to_login(next, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME): |
||||
"Redirects the user to the login page, passing the given 'next' page" |
||||
if not login_url: |
||||
login_url = settings.LOGIN_URL |
||||
return HttpResponseRedirect('%s?%s=%s' % (login_url, urlquote(redirect_field_name), urlquote(next))) |
||||
|
||||
# 4 views for password reset: |
||||
# - password_reset sends the mail |
||||
# - password_reset_done shows a success message for the above |
||||
# - password_reset_confirm checks the link the user clicked and |
||||
# prompts for a new password |
||||
# - password_reset_complete shows a success message for the above |
||||
|
||||
def password_reset(request, is_admin_site=False, template_name='registration/password_reset_form.html', |
||||
email_template_name='registration/password_reset_email.html', |
||||
password_reset_form=PasswordResetForm, token_generator=default_token_generator, |
||||
post_reset_redirect=None): |
||||
if post_reset_redirect is None: |
||||
post_reset_redirect = reverse('django.contrib.auth.views.password_reset_done') |
||||
if request.method == "POST": |
||||
form = password_reset_form(request.POST) |
||||
if form.is_valid(): |
||||
opts = {} |
||||
opts['use_https'] = request.is_secure() |
||||
opts['token_generator'] = token_generator |
||||
if is_admin_site: |
||||
opts['domain_override'] = request.META['HTTP_HOST'] |
||||
else: |
||||
opts['email_template_name'] = email_template_name |
||||
if not Site._meta.installed: |
||||
opts['domain_override'] = RequestSite(request).domain |
||||
form.save(**opts) |
||||
return HttpResponseRedirect(post_reset_redirect) |
||||
else: |
||||
form = password_reset_form() |
||||
return render_to_response(template_name, { |
||||
'form': form, |
||||
}, context_instance=RequestContext(request)) |
||||
|
||||
def password_reset_done(request, template_name='registration/password_reset_done.html'): |
||||
return render_to_response(template_name, context_instance=RequestContext(request)) |
||||
|
||||
def password_reset_confirm(request, uidb36=None, token=None, template_name='registration/password_reset_confirm.html', |
||||
token_generator=default_token_generator, set_password_form=SetPasswordForm, |
||||
post_reset_redirect=None): |
||||
""" |
||||
View that checks the hash in a password reset link and presents a |
||||
form for entering a new password. |
||||
""" |
||||
assert uidb36 is not None and token is not None # checked by URLconf |
||||
if post_reset_redirect is None: |
||||
post_reset_redirect = reverse('django.contrib.auth.views.password_reset_complete') |
||||
try: |
||||
uid_int = base36_to_int(uidb36) |
||||
except ValueError: |
||||
raise Http404 |
||||
|
||||
user = get_object_or_404(User, id=uid_int) |
||||
context_instance = RequestContext(request) |
||||
|
||||
if token_generator.check_token(user, token): |
||||
context_instance['validlink'] = True |
||||
if request.method == 'POST': |
||||
form = set_password_form(user, request.POST) |
||||
if form.is_valid(): |
||||
form.save() |
||||
return HttpResponseRedirect(post_reset_redirect) |
||||
else: |
||||
form = set_password_form(None) |
||||
else: |
||||
context_instance['validlink'] = False |
||||
form = None |
||||
context_instance['form'] = form |
||||
return render_to_response(template_name, context_instance=context_instance) |
||||
|
||||
def password_reset_complete(request, template_name='registration/password_reset_complete.html'): |
||||
return render_to_response(template_name, context_instance=RequestContext(request, |
||||
{'login_url': settings.LOGIN_URL})) |
||||
|
||||
def password_change(request, template_name='registration/password_change_form.html', |
||||
post_change_redirect=None): |
||||
if post_change_redirect is None: |
||||
post_change_redirect = reverse('django.contrib.auth.views.password_change_done') |
||||
if request.method == "POST": |
||||
form = PasswordChangeForm(request.user, request.POST) |
||||
if form.is_valid(): |
||||
form.save() |
||||
return HttpResponseRedirect(post_change_redirect) |
||||
else: |
||||
form = PasswordChangeForm(request.user) |
||||
return render_to_response(template_name, { |
||||
'form': form, |
||||
}, context_instance=RequestContext(request)) |
||||
password_change = login_required(password_change) |
||||
|
||||
def password_change_done(request, template_name='registration/password_change_done.html'): |
||||
return render_to_response(template_name, context_instance=RequestContext(request)) |
||||
@ -0,0 +1,77 @@
@@ -0,0 +1,77 @@
|
||||
from django.conf import settings |
||||
from django.core import urlresolvers |
||||
from django.core.exceptions import ImproperlyConfigured |
||||
|
||||
# Attributes required in the top-level app for COMMENTS_APP |
||||
REQUIRED_COMMENTS_APP_ATTRIBUTES = ["get_model", "get_form", "get_form_target"] |
||||
|
||||
def get_comment_app(): |
||||
""" |
||||
Get the comment app (i.e. "django.contrib.comments") as defined in the settings |
||||
""" |
||||
# Make sure the app's in INSTALLED_APPS |
||||
comments_app = get_comment_app_name() |
||||
if comments_app not in settings.INSTALLED_APPS: |
||||
raise ImproperlyConfigured("The COMMENTS_APP (%r) "\ |
||||
"must be in INSTALLED_APPS" % settings.COMMENTS_APP) |
||||
|
||||
# Try to import the package |
||||
try: |
||||
package = __import__(comments_app, '', '', ['']) |
||||
except ImportError: |
||||
raise ImproperlyConfigured("The COMMENTS_APP setting refers to "\ |
||||
"a non-existing package.") |
||||
|
||||
# Make sure some specific attributes exist inside that package. |
||||
for attribute in REQUIRED_COMMENTS_APP_ATTRIBUTES: |
||||
if not hasattr(package, attribute): |
||||
raise ImproperlyConfigured("The COMMENTS_APP package %r does not "\ |
||||
"define the (required) %r function" % \ |
||||
(package, attribute)) |
||||
|
||||
return package |
||||
|
||||
def get_comment_app_name(): |
||||
""" |
||||
Returns the name of the comment app (either the setting value, if it |
||||
exists, or the default). |
||||
""" |
||||
return getattr(settings, 'COMMENTS_APP', 'django.contrib.comments') |
||||
|
||||
def get_model(): |
||||
from django.contrib.comments.models import Comment |
||||
return Comment |
||||
|
||||
def get_form(): |
||||
from django.contrib.comments.forms import CommentForm |
||||
return CommentForm |
||||
|
||||
def get_form_target(): |
||||
return urlresolvers.reverse("django.contrib.comments.views.comments.post_comment") |
||||
|
||||
def get_flag_url(comment): |
||||
""" |
||||
Get the URL for the "flag this comment" view. |
||||
""" |
||||
if get_comment_app_name() != __name__ and hasattr(get_comment_app(), "get_flag_url"): |
||||
return get_comment_app().get_flag_url(comment) |
||||
else: |
||||
return urlresolvers.reverse("django.contrib.comments.views.moderation.flag", args=(comment.id,)) |
||||
|
||||
def get_delete_url(comment): |
||||
""" |
||||
Get the URL for the "delete this comment" view. |
||||
""" |
||||
if get_comment_app_name() != __name__ and hasattr(get_comment_app(), "get_delete_url"): |
||||
return get_comment_app().get_flag_url(get_delete_url) |
||||
else: |
||||
return urlresolvers.reverse("django.contrib.comments.views.moderation.delete", args=(comment.id,)) |
||||
|
||||
def get_approve_url(comment): |
||||
""" |
||||
Get the URL for the "approve this comment from moderation" view. |
||||
""" |
||||
if get_comment_app_name() != __name__ and hasattr(get_comment_app(), "get_approve_url"): |
||||
return get_comment_app().get_approve_url(comment) |
||||
else: |
||||
return urlresolvers.reverse("django.contrib.comments.views.moderation.approve", args=(comment.id,)) |
||||
@ -0,0 +1,24 @@
@@ -0,0 +1,24 @@
|
||||
from django.contrib import admin |
||||
from django.conf import settings |
||||
from django.contrib.comments.models import Comment |
||||
from django.utils.translation import ugettext_lazy as _ |
||||
|
||||
class CommentsAdmin(admin.ModelAdmin): |
||||
fieldsets = ( |
||||
(None, |
||||
{'fields': ('content_type', 'object_pk', 'site')} |
||||
), |
||||
(_('Content'), |
||||
{'fields': ('user', 'user_name', 'user_email', 'user_url', 'comment')} |
||||
), |
||||
(_('Metadata'), |
||||
{'fields': ('submit_date', 'ip_address', 'is_public', 'is_removed')} |
||||
), |
||||
) |
||||
|
||||
list_display = ('name', 'content_type', 'object_pk', 'ip_address', 'is_public', 'is_removed') |
||||
list_filter = ('submit_date', 'site', 'is_public', 'is_removed') |
||||
date_hierarchy = 'submit_date' |
||||
search_fields = ('comment', 'user__username', 'user_name', 'user_email', 'user_url', 'ip_address') |
||||
|
||||
admin.site.register(Comment, CommentsAdmin) |
||||
@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
|
||||
from django.conf import settings |
||||
from django.contrib.syndication.feeds import Feed |
||||
from django.contrib.sites.models import Site |
||||
from django.contrib import comments |
||||
|
||||
class LatestCommentFeed(Feed): |
||||
"""Feed of latest comments on the current site.""" |
||||
|
||||
def title(self): |
||||
if not hasattr(self, '_site'): |
||||
self._site = Site.objects.get_current() |
||||
return u"%s comments" % self._site.name |
||||
|
||||
def link(self): |
||||
if not hasattr(self, '_site'): |
||||
self._site = Site.objects.get_current() |
||||
return "http://%s/" % (self._site.domain) |
||||
|
||||
def description(self): |
||||
if not hasattr(self, '_site'): |
||||
self._site = Site.objects.get_current() |
||||
return u"Latest comments on %s" % self._site.name |
||||
|
||||
def items(self): |
||||
qs = comments.get_model().objects.filter( |
||||
site__pk = settings.SITE_ID, |
||||
is_public = True, |
||||
is_removed = False, |
||||
) |
||||
if getattr(settings, 'COMMENTS_BANNED_USERS_GROUP', None): |
||||
where = ['user_id NOT IN (SELECT user_id FROM auth_users_group WHERE group_id = %s)'] |
||||
params = [settings.COMMENTS_BANNED_USERS_GROUP] |
||||
qs = qs.extra(where=where, params=params) |
||||
return qs[:40] |
||||
|
||||
def item_pubdate(self, item): |
||||
return item.submit_date |
||||
@ -0,0 +1,158 @@
@@ -0,0 +1,158 @@
|
||||
import re |
||||
import time |
||||
import datetime |
||||
|
||||
from django import forms |
||||
from django.forms.util import ErrorDict |
||||
from django.conf import settings |
||||
from django.http import Http404 |
||||
from django.contrib.contenttypes.models import ContentType |
||||
from models import Comment |
||||
from django.utils.encoding import force_unicode |
||||
from django.utils.hashcompat import sha_constructor |
||||
from django.utils.text import get_text_list |
||||
from django.utils.translation import ngettext |
||||
from django.utils.translation import ugettext_lazy as _ |
||||
|
||||
COMMENT_MAX_LENGTH = getattr(settings,'COMMENT_MAX_LENGTH', 3000) |
||||
|
||||
class CommentForm(forms.Form): |
||||
name = forms.CharField(label=_("Name"), max_length=50) |
||||
email = forms.EmailField(label=_("Email address")) |
||||
url = forms.URLField(label=_("URL"), required=False) |
||||
comment = forms.CharField(label=_('Comment'), widget=forms.Textarea, |
||||
max_length=COMMENT_MAX_LENGTH) |
||||
honeypot = forms.CharField(required=False, |
||||
label=_('If you enter anything in this field '\ |
||||
'your comment will be treated as spam')) |
||||
content_type = forms.CharField(widget=forms.HiddenInput) |
||||
object_pk = forms.CharField(widget=forms.HiddenInput) |
||||
timestamp = forms.IntegerField(widget=forms.HiddenInput) |
||||
security_hash = forms.CharField(min_length=40, max_length=40, widget=forms.HiddenInput) |
||||
|
||||
def __init__(self, target_object, data=None, initial=None): |
||||
self.target_object = target_object |
||||
if initial is None: |
||||
initial = {} |
||||
initial.update(self.generate_security_data()) |
||||
super(CommentForm, self).__init__(data=data, initial=initial) |
||||
|
||||
def get_comment_object(self): |
||||
""" |
||||
Return a new (unsaved) comment object based on the information in this |
||||
form. Assumes that the form is already validated and will throw a |
||||
ValueError if not. |
||||
|
||||
Does not set any of the fields that would come from a Request object |
||||
(i.e. ``user`` or ``ip_address``). |
||||
""" |
||||
if not self.is_valid(): |
||||
raise ValueError("get_comment_object may only be called on valid forms") |
||||
|
||||
new = Comment( |
||||
content_type = ContentType.objects.get_for_model(self.target_object), |
||||
object_pk = force_unicode(self.target_object._get_pk_val()), |
||||
user_name = self.cleaned_data["name"], |
||||
user_email = self.cleaned_data["email"], |
||||
user_url = self.cleaned_data["url"], |
||||
comment = self.cleaned_data["comment"], |
||||
submit_date = datetime.datetime.now(), |
||||
site_id = settings.SITE_ID, |
||||
is_public = True, |
||||
is_removed = False, |
||||
) |
||||
|
||||
# Check that this comment isn't duplicate. (Sometimes people post comments |
||||
# twice by mistake.) If it is, fail silently by returning the old comment. |
||||
possible_duplicates = Comment.objects.filter( |
||||
content_type = new.content_type, |
||||
object_pk = new.object_pk, |
||||
user_name = new.user_name, |
||||
user_email = new.user_email, |
||||
user_url = new.user_url, |
||||
) |
||||
for old in possible_duplicates: |
||||
if old.submit_date.date() == new.submit_date.date() and old.comment == new.comment: |
||||
return old |
||||
|
||||
return new |
||||
|
||||
def security_errors(self): |
||||
"""Return just those errors associated with security""" |
||||
errors = ErrorDict() |
||||
for f in ["honeypot", "timestamp", "security_hash"]: |
||||
if f in self.errors: |
||||
errors[f] = self.errors[f] |
||||
return errors |
||||
|
||||
def clean_honeypot(self): |
||||
"""Check that nothing's been entered into the honeypot.""" |
||||
value = self.cleaned_data["honeypot"] |
||||
if value: |
||||
raise forms.ValidationError(self.fields["honeypot"].label) |
||||
return value |
||||
|
||||
def clean_security_hash(self): |
||||
"""Check the security hash.""" |
||||
security_hash_dict = { |
||||
'content_type' : self.data.get("content_type", ""), |
||||
'object_pk' : self.data.get("object_pk", ""), |
||||
'timestamp' : self.data.get("timestamp", ""), |
||||
} |
||||
expected_hash = self.generate_security_hash(**security_hash_dict) |
||||
actual_hash = self.cleaned_data["security_hash"] |
||||
if expected_hash != actual_hash: |
||||
raise forms.ValidationError("Security hash check failed.") |
||||
return actual_hash |
||||
|
||||
def clean_timestamp(self): |
||||
"""Make sure the timestamp isn't too far (> 2 hours) in the past.""" |
||||
ts = self.cleaned_data["timestamp"] |
||||
if time.time() - ts > (2 * 60 * 60): |
||||
raise forms.ValidationError("Timestamp check failed") |
||||
return ts |
||||
|
||||
def clean_comment(self): |
||||
""" |
||||
If COMMENTS_ALLOW_PROFANITIES is False, check that the comment doesn't |
||||
contain anything in PROFANITIES_LIST. |
||||
""" |
||||
comment = self.cleaned_data["comment"] |
||||
if settings.COMMENTS_ALLOW_PROFANITIES == False: |
||||
bad_words = [w for w in settings.PROFANITIES_LIST if w in comment.lower()] |
||||
if bad_words: |
||||
plural = len(bad_words) > 1 |
||||
raise forms.ValidationError(ngettext( |
||||
"Watch your mouth! The word %s is not allowed here.", |
||||
"Watch your mouth! The words %s are not allowed here.", plural) % \ |
||||
get_text_list(['"%s%s%s"' % (i[0], '-'*(len(i)-2), i[-1]) for i in bad_words], 'and')) |
||||
return comment |
||||
|
||||
def generate_security_data(self): |
||||
"""Generate a dict of security data for "initial" data.""" |
||||
timestamp = int(time.time()) |
||||
security_dict = { |
||||
'content_type' : str(self.target_object._meta), |
||||
'object_pk' : str(self.target_object._get_pk_val()), |
||||
'timestamp' : str(timestamp), |
||||
'security_hash' : self.initial_security_hash(timestamp), |
||||
} |
||||
return security_dict |
||||
|
||||
def initial_security_hash(self, timestamp): |
||||
""" |
||||
Generate the initial security hash from self.content_object |
||||
and a (unix) timestamp. |
||||
""" |
||||
|
||||
initial_security_dict = { |
||||
'content_type' : str(self.target_object._meta), |
||||
'object_pk' : str(self.target_object._get_pk_val()), |
||||
'timestamp' : str(timestamp), |
||||
} |
||||
return self.generate_security_hash(**initial_security_dict) |
||||
|
||||
def generate_security_hash(self, content_type, object_pk, timestamp): |
||||
"""Generate a (SHA1) security hash from the provided info.""" |
||||
info = (content_type, object_pk, timestamp, settings.SECRET_KEY) |
||||
return sha_constructor("".join(info)).hexdigest() |
||||
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
from django.db import models |
||||
from django.dispatch import dispatcher |
||||
from django.contrib.contenttypes.models import ContentType |
||||
from django.utils.encoding import force_unicode |
||||
|
||||
class CommentManager(models.Manager): |
||||
|
||||
def in_moderation(self): |
||||
""" |
||||
QuerySet for all comments currently in the moderation queue. |
||||
""" |
||||
return self.get_query_set().filter(is_public=False, is_removed=False) |
||||
|
||||
def for_model(self, model): |
||||
""" |
||||
QuerySet for all comments for a particular model (either an instance or |
||||
a class). |
||||
""" |
||||
ct = ContentType.objects.get_for_model(model) |
||||
qs = self.get_query_set().filter(content_type=ct) |
||||
if isinstance(model, models.Model): |
||||
qs = qs.filter(object_pk=force_unicode(model._get_pk_val())) |
||||
return qs |
||||
@ -0,0 +1,185 @@
@@ -0,0 +1,185 @@
|
||||
import datetime |
||||
from django.contrib.auth.models import User |
||||
from django.contrib.comments.managers import CommentManager |
||||
from django.contrib.contenttypes import generic |
||||
from django.contrib.contenttypes.models import ContentType |
||||
from django.contrib.sites.models import Site |
||||
from django.db import models |
||||
from django.core import urlresolvers |
||||
from django.utils.translation import ugettext_lazy as _ |
||||
from django.conf import settings |
||||
|
||||
COMMENT_MAX_LENGTH = getattr(settings,'COMMENT_MAX_LENGTH',3000) |
||||
|
||||
class BaseCommentAbstractModel(models.Model): |
||||
""" |
||||
An abstract base class that any custom comment models probably should |
||||
subclass. |
||||
""" |
||||
|
||||
# Content-object field |
||||
content_type = models.ForeignKey(ContentType, |
||||
related_name="content_type_set_for_%(class)s") |
||||
object_pk = models.TextField(_('object ID')) |
||||
content_object = generic.GenericForeignKey(ct_field="content_type", fk_field="object_pk") |
||||
|
||||
# Metadata about the comment |
||||
site = models.ForeignKey(Site) |
||||
|
||||
class Meta: |
||||
abstract = True |
||||
|
||||
def get_content_object_url(self): |
||||
""" |
||||
Get a URL suitable for redirecting to the content object. |
||||
""" |
||||
return urlresolvers.reverse( |
||||
"comments-url-redirect", |
||||
args=(self.content_type_id, self.object_pk) |
||||
) |
||||
|
||||
class Comment(BaseCommentAbstractModel): |
||||
""" |
||||
A user comment about some object. |
||||
""" |
||||
|
||||
# Who posted this comment? If ``user`` is set then it was an authenticated |
||||
# user; otherwise at least user_name should have been set and the comment |
||||
# was posted by a non-authenticated user. |
||||
user = models.ForeignKey(User, blank=True, null=True, related_name="%(class)s_comments") |
||||
user_name = models.CharField(_("user's name"), max_length=50, blank=True) |
||||
user_email = models.EmailField(_("user's email address"), blank=True) |
||||
user_url = models.URLField(_("user's URL"), blank=True) |
||||
|
||||
comment = models.TextField(_('comment'), max_length=COMMENT_MAX_LENGTH) |
||||
|
||||
# Metadata about the comment |
||||
submit_date = models.DateTimeField(_('date/time submitted'), default=None) |
||||
ip_address = models.IPAddressField(_('IP address'), blank=True, null=True) |
||||
is_public = models.BooleanField(_('is public'), default=True, |
||||
help_text=_('Uncheck this box to make the comment effectively ' \ |
||||
'disappear from the site.')) |
||||
is_removed = models.BooleanField(_('is removed'), default=False, |
||||
help_text=_('Check this box if the comment is inappropriate. ' \ |
||||
'A "This comment has been removed" message will ' \ |
||||
'be displayed instead.')) |
||||
|
||||
# Manager |
||||
objects = CommentManager() |
||||
|
||||
class Meta: |
||||
db_table = "django_comments" |
||||
ordering = ('submit_date',) |
||||
permissions = [("can_moderate", "Can moderate comments")] |
||||
|
||||
def __unicode__(self): |
||||
return "%s: %s..." % (self.name, self.comment[:50]) |
||||
|
||||
def save(self, force_insert=False, force_update=False): |
||||
if self.submit_date is None: |
||||
self.submit_date = datetime.datetime.now() |
||||
super(Comment, self).save(force_insert, force_update) |
||||
|
||||
def _get_userinfo(self): |
||||
""" |
||||
Get a dictionary that pulls together information about the poster |
||||
safely for both authenticated and non-authenticated comments. |
||||
|
||||
This dict will have ``name``, ``email``, and ``url`` fields. |
||||
""" |
||||
if not hasattr(self, "_userinfo"): |
||||
self._userinfo = { |
||||
"name" : self.user_name, |
||||
"email" : self.user_email, |
||||
"url" : self.user_url |
||||
} |
||||
if self.user_id: |
||||
u = self.user |
||||
if u.email: |
||||
self._userinfo["email"] = u.email |
||||
|
||||
# If the user has a full name, use that for the user name. |
||||
# However, a given user_name overrides the raw user.username, |
||||
# so only use that if this comment has no associated name. |
||||
if u.get_full_name(): |
||||
self._userinfo["name"] = self.user.get_full_name() |
||||
elif not self.user_name: |
||||
self._userinfo["name"] = u.username |
||||
return self._userinfo |
||||
userinfo = property(_get_userinfo, doc=_get_userinfo.__doc__) |
||||
|
||||
def _get_name(self): |
||||
return self.userinfo["name"] |
||||
def _set_name(self, val): |
||||
if self.user_id: |
||||
raise AttributeError(_("This comment was posted by an authenticated "\ |
||||
"user and thus the name is read-only.")) |
||||
self.user_name = val |
||||
name = property(_get_name, _set_name, doc="The name of the user who posted this comment") |
||||
|
||||
def _get_email(self): |
||||
return self.userinfo["email"] |
||||
def _set_email(self, val): |
||||
if self.user_id: |
||||
raise AttributeError(_("This comment was posted by an authenticated "\ |
||||
"user and thus the email is read-only.")) |
||||
self.user_email = val |
||||
email = property(_get_email, _set_email, doc="The email of the user who posted this comment") |
||||
|
||||
def _get_url(self): |
||||
return self.userinfo["url"] |
||||
def _set_url(self, val): |
||||
self.user_url = val |
||||
url = property(_get_url, _set_url, doc="The URL given by the user who posted this comment") |
||||
|
||||
def get_absolute_url(self, anchor_pattern="#c%(id)s"): |
||||
return self.get_content_object_url() + (anchor_pattern % self.__dict__) |
||||
|
||||
def get_as_text(self): |
||||
""" |
||||
Return this comment as plain text. Useful for emails. |
||||
""" |
||||
d = { |
||||
'user': self.user, |
||||
'date': self.submit_date, |
||||
'comment': self.comment, |
||||
'domain': self.site.domain, |
||||
'url': self.get_absolute_url() |
||||
} |
||||
return _('Posted by %(user)s at %(date)s\n\n%(comment)s\n\nhttp://%(domain)s%(url)s') % d |
||||
|
||||
class CommentFlag(models.Model): |
||||
""" |
||||
Records a flag on a comment. This is intentionally flexible; right now, a |
||||
flag could be: |
||||
|
||||
* A "removal suggestion" -- where a user suggests a comment for (potential) removal. |
||||
|
||||
* A "moderator deletion" -- used when a moderator deletes a comment. |
||||
|
||||
You can (ab)use this model to add other flags, if needed. However, by |
||||
design users are only allowed to flag a comment with a given flag once; |
||||
if you want rating look elsewhere. |
||||
""" |
||||
user = models.ForeignKey(User, related_name="comment_flags") |
||||
comment = models.ForeignKey(Comment, related_name="flags") |
||||
flag = models.CharField(max_length=30, db_index=True) |
||||
flag_date = models.DateTimeField(default=None) |
||||
|
||||
# Constants for flag types |
||||
SUGGEST_REMOVAL = "removal suggestion" |
||||
MODERATOR_DELETION = "moderator deletion" |
||||
MODERATOR_APPROVAL = "moderator approval" |
||||
|
||||
class Meta: |
||||
db_table = 'django_comment_flags' |
||||
unique_together = [('user', 'comment', 'flag')] |
||||
|
||||
def __unicode__(self): |
||||
return "%s flag of comment ID %s by %s" % \ |
||||
(self.flag, self.comment_id, self.user.username) |
||||
|
||||
def save(self, force_insert=False, force_update=False): |
||||
if self.flag_date is None: |
||||
self.flag_date = datetime.datetime.now() |
||||
super(CommentFlag, self).save(force_insert, force_update) |
||||
@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
|
||||
""" |
||||
Signals relating to comments. |
||||
""" |
||||
from django.dispatch import Signal |
||||
|
||||
# Sent just before a comment will be posted (after it's been approved and |
||||
# moderated; this can be used to modify the comment (in place) with posting |
||||
# details or other such actions. If any receiver returns False the comment will be |
||||
# discarded and a 403 (not allowed) response. This signal is sent at more or less |
||||
# the same time (just before, actually) as the Comment object's pre-save signal, |
||||
# except that the HTTP request is sent along with this signal. |
||||
comment_will_be_posted = Signal(providing_args=["comment", "request"]) |
||||
|
||||
# Sent just after a comment was posted. See above for how this differs |
||||
# from the Comment object's post-save signal. |
||||
comment_was_posted = Signal(providing_args=["comment", "request"]) |
||||
|
||||
# Sent after a comment was "flagged" in some way. Check the flag to see if this |
||||
# was a user requesting removal of a comment, a moderator approving/removing a |
||||
# comment, or some other custom user flag. |
||||
comment_was_flagged = Signal(providing_args=["comment", "flag", "created", "request"]) |
||||
@ -0,0 +1,252 @@
@@ -0,0 +1,252 @@
|
||||
from django import template |
||||
from django.template.loader import render_to_string |
||||
from django.conf import settings |
||||
from django.contrib.contenttypes.models import ContentType |
||||
from django.contrib import comments |
||||
from django.utils.encoding import smart_unicode |
||||
|
||||
register = template.Library() |
||||
|
||||
class BaseCommentNode(template.Node): |
||||
""" |
||||
Base helper class (abstract) for handling the get_comment_* template tags. |
||||
Looks a bit strange, but the subclasses below should make this a bit more |
||||
obvious. |
||||
""" |
||||
|
||||
#@classmethod |
||||
def handle_token(cls, parser, token): |
||||
"""Class method to parse get_comment_list/count/form and return a Node.""" |
||||
tokens = token.contents.split() |
||||
if tokens[1] != 'for': |
||||
raise template.TemplateSyntaxError("Second argument in %r tag must be 'for'" % tokens[0]) |
||||
|
||||
# {% get_whatever for obj as varname %} |
||||
if len(tokens) == 5: |
||||
if tokens[3] != 'as': |
||||
raise template.TemplateSyntaxError("Third argument in %r must be 'as'" % tokens[0]) |
||||
return cls( |
||||
object_expr = parser.compile_filter(tokens[2]), |
||||
as_varname = tokens[4], |
||||
) |
||||
|
||||
# {% get_whatever for app.model pk as varname %} |
||||
elif len(tokens) == 6: |
||||
if tokens[4] != 'as': |
||||
raise template.TemplateSyntaxError("Fourth argument in %r must be 'as'" % tokens[0]) |
||||
return cls( |
||||
ctype = BaseCommentNode.lookup_content_type(tokens[2], tokens[0]), |
||||
object_pk_expr = parser.compile_filter(tokens[3]), |
||||
as_varname = tokens[5] |
||||
) |
||||
|
||||
else: |
||||
raise template.TemplateSyntaxError("%r tag requires 4 or 5 arguments" % tokens[0]) |
||||
|
||||
handle_token = classmethod(handle_token) |
||||
|
||||
#@staticmethod |
||||
def lookup_content_type(token, tagname): |
||||
try: |
||||
app, model = token.split('.') |
||||
return ContentType.objects.get(app_label=app, model=model) |
||||
except ValueError: |
||||
raise template.TemplateSyntaxError("Third argument in %r must be in the format 'app.model'" % tagname) |
||||
except ContentType.DoesNotExist: |
||||
raise template.TemplateSyntaxError("%r tag has non-existant content-type: '%s.%s'" % (tagname, app, model)) |
||||
lookup_content_type = staticmethod(lookup_content_type) |
||||
|
||||
def __init__(self, ctype=None, object_pk_expr=None, object_expr=None, as_varname=None, comment=None): |
||||
if ctype is None and object_expr is None: |
||||
raise template.TemplateSyntaxError("Comment nodes must be given either a literal object or a ctype and object pk.") |
||||
self.comment_model = comments.get_model() |
||||
self.as_varname = as_varname |
||||
self.ctype = ctype |
||||
self.object_pk_expr = object_pk_expr |
||||
self.object_expr = object_expr |
||||
self.comment = comment |
||||
|
||||
def render(self, context): |
||||
qs = self.get_query_set(context) |
||||
context[self.as_varname] = self.get_context_value_from_queryset(context, qs) |
||||
return '' |
||||
|
||||
def get_query_set(self, context): |
||||
ctype, object_pk = self.get_target_ctype_pk(context) |
||||
if not object_pk: |
||||
return self.comment_model.objects.none() |
||||
|
||||
qs = self.comment_model.objects.filter( |
||||
content_type = ctype, |
||||
object_pk = smart_unicode(object_pk), |
||||
site__pk = settings.SITE_ID, |
||||
is_public = True, |
||||
) |
||||
if getattr(settings, 'COMMENTS_HIDE_REMOVED', True): |
||||
qs = qs.filter(is_removed=False) |
||||
|
||||
return qs |
||||
|
||||
def get_target_ctype_pk(self, context): |
||||
if self.object_expr: |
||||
try: |
||||
obj = self.object_expr.resolve(context) |
||||
except template.VariableDoesNotExist: |
||||
return None, None |
||||
return ContentType.objects.get_for_model(obj), obj.pk |
||||
else: |
||||
return self.ctype, self.object_pk_expr.resolve(context, ignore_failures=True) |
||||
|
||||
def get_context_value_from_queryset(self, context, qs): |
||||
"""Subclasses should override this.""" |
||||
raise NotImplementedError |
||||
|
||||
class CommentListNode(BaseCommentNode): |
||||
"""Insert a list of comments into the context.""" |
||||
def get_context_value_from_queryset(self, context, qs): |
||||
return list(qs) |
||||
|
||||
class CommentCountNode(BaseCommentNode): |
||||
"""Insert a count of comments into the context.""" |
||||
def get_context_value_from_queryset(self, context, qs): |
||||
return qs.count() |
||||
|
||||
class CommentFormNode(BaseCommentNode): |
||||
"""Insert a form for the comment model into the context.""" |
||||
|
||||
def get_form(self, context): |
||||
ctype, object_pk = self.get_target_ctype_pk(context) |
||||
if object_pk: |
||||
return comments.get_form()(ctype.get_object_for_this_type(pk=object_pk)) |
||||
else: |
||||
return None |
||||
|
||||
def render(self, context): |
||||
context[self.as_varname] = self.get_form(context) |
||||
return '' |
||||
|
||||
class RenderCommentFormNode(CommentFormNode): |
||||
"""Render the comment form directly""" |
||||
|
||||
#@classmethod |
||||
def handle_token(cls, parser, token): |
||||
"""Class method to parse render_comment_form and return a Node.""" |
||||
tokens = token.contents.split() |
||||
if tokens[1] != 'for': |
||||
raise template.TemplateSyntaxError("Second argument in %r tag must be 'for'" % tokens[0]) |
||||
|
||||
# {% render_comment_form for obj %} |
||||
if len(tokens) == 3: |
||||
return cls(object_expr=parser.compile_filter(tokens[2])) |
||||
|
||||
# {% render_comment_form for app.models pk %} |
||||
elif len(tokens) == 4: |
||||
return cls( |
||||
ctype = BaseCommentNode.lookup_content_type(tokens[2], tokens[0]), |
||||
object_pk_expr = parser.compile_filter(tokens[3]) |
||||
) |
||||
handle_token = classmethod(handle_token) |
||||
|
||||
def render(self, context): |
||||
ctype, object_pk = self.get_target_ctype_pk(context) |
||||
if object_pk: |
||||
template_search_list = [ |
||||
"comments/%s/%s/form.html" % (ctype.app_label, ctype.model), |
||||
"comments/%s/form.html" % ctype.app_label, |
||||
"comments/form.html" |
||||
] |
||||
context.push() |
||||
formstr = render_to_string(template_search_list, {"form" : self.get_form(context)}, context) |
||||
context.pop() |
||||
return formstr |
||||
else: |
||||
return '' |
||||
|
||||
# We could just register each classmethod directly, but then we'd lose out on |
||||
# the automagic docstrings-into-admin-docs tricks. So each node gets a cute |
||||
# wrapper function that just exists to hold the docstring. |
||||
|
||||
#@register.tag |
||||
def get_comment_count(parser, token): |
||||
""" |
||||
Gets the comment count for the given params and populates the template |
||||
context with a variable containing that value, whose name is defined by the |
||||
'as' clause. |
||||
|
||||
Syntax:: |
||||
|
||||
{% get_comment_count for [object] as [varname] %} |
||||
{% get_comment_count for [app].[model] [object_id] as [varname] %} |
||||
|
||||
Example usage:: |
||||
|
||||
{% get_comment_count for event as comment_count %} |
||||
{% get_comment_count for calendar.event event.id as comment_count %} |
||||
{% get_comment_count for calendar.event 17 as comment_count %} |
||||
|
||||
""" |
||||
return CommentCountNode.handle_token(parser, token) |
||||
|
||||
#@register.tag |
||||
def get_comment_list(parser, token): |
||||
""" |
||||
Gets the list of comments for the given params and populates the template |
||||
context with a variable containing that value, whose name is defined by the |
||||
'as' clause. |
||||
|
||||
Syntax:: |
||||
|
||||
{% get_comment_list for [object] as [varname] %} |
||||
{% get_comment_list for [app].[model] [object_id] as [varname] %} |
||||
|
||||
Example usage:: |
||||
|
||||
{% get_comment_list for event as comment_list %} |
||||
{% for comment in comment_list %} |
||||
... |
||||
{% endfor %} |
||||
|
||||
""" |
||||
return CommentListNode.handle_token(parser, token) |
||||
|
||||
#@register.tag |
||||
def get_comment_form(parser, token): |
||||
""" |
||||
Get a (new) form object to post a new comment. |
||||
|
||||
Syntax:: |
||||
|
||||
{% get_comment_form for [object] as [varname] %} |
||||
{% get_comment_form for [app].[model] [object_id] as [varname] %} |
||||
""" |
||||
return CommentFormNode.handle_token(parser, token) |
||||
|
||||
#@register.tag |
||||
def render_comment_form(parser, token): |
||||
""" |
||||
Render the comment form (as returned by ``{% render_comment_form %}``) through |
||||
the ``comments/form.html`` template. |
||||
|
||||
Syntax:: |
||||
|
||||
{% render_comment_form for [object] %} |
||||
{% render_comment_form for [app].[model] [object_id] %} |
||||
""" |
||||
return RenderCommentFormNode.handle_token(parser, token) |
||||
|
||||
#@register.simple_tag |
||||
def comment_form_target(): |
||||
""" |
||||
Get the target URL for the comment form. |
||||
|
||||
Example:: |
||||
|
||||
<form action="{% comment_form_target %}" method="POST"> |
||||
""" |
||||
return comments.get_form_target() |
||||
|
||||
register.tag(get_comment_count) |
||||
register.tag(get_comment_list) |
||||
register.tag(get_comment_form) |
||||
register.tag(render_comment_form) |
||||
register.simple_tag(comment_form_target) |
||||
@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
|
||||
from django.conf.urls.defaults import * |
||||
from django.conf import settings |
||||
|
||||
urlpatterns = patterns('django.contrib.comments.views', |
||||
url(r'^post/$', 'comments.post_comment', name='comments-post-comment'), |
||||
url(r'^posted/$', 'comments.comment_done', name='comments-comment-done'), |
||||
url(r'^flag/(\d+)/$', 'moderation.flag', name='comments-flag'), |
||||
url(r'^flagged/$', 'moderation.flag_done', name='comments-flag-done'), |
||||
url(r'^delete/(\d+)/$', 'moderation.delete', name='comments-delete'), |
||||
url(r'^deleted/$', 'moderation.delete_done', name='comments-delete-done'), |
||||
url(r'^moderate/$', 'moderation.moderation_queue', name='comments-moderation-queue'), |
||||
url(r'^approve/(\d+)/$', 'moderation.approve', name='comments-approve'), |
||||
url(r'^approved/$', 'moderation.approve_done', name='comments-approve-done'), |
||||
) |
||||
|
||||
urlpatterns += patterns('', |
||||
url(r'^cr/(\d+)/(\w+)/$', 'django.views.defaults.shortcut', name='comments-url-redirect'), |
||||
) |
||||
|
||||
@ -0,0 +1,124 @@
@@ -0,0 +1,124 @@
|
||||
from django import http |
||||
from django.conf import settings |
||||
from utils import next_redirect, confirmation_view |
||||
from django.core.exceptions import ObjectDoesNotExist |
||||
from django.db import models |
||||
from django.shortcuts import render_to_response |
||||
from django.template import RequestContext |
||||
from django.template.loader import render_to_string |
||||
from django.utils.html import escape |
||||
from django.contrib import comments |
||||
from django.contrib.comments import signals |
||||
|
||||
class CommentPostBadRequest(http.HttpResponseBadRequest): |
||||
""" |
||||
Response returned when a comment post is invalid. If ``DEBUG`` is on a |
||||
nice-ish error message will be displayed (for debugging purposes), but in |
||||
production mode a simple opaque 400 page will be displayed. |
||||
""" |
||||
def __init__(self, why): |
||||
super(CommentPostBadRequest, self).__init__() |
||||
if settings.DEBUG: |
||||
self.content = render_to_string("comments/400-debug.html", {"why": why}) |
||||
|
||||
def post_comment(request, next=None): |
||||
""" |
||||
Post a comment. |
||||
|
||||
HTTP POST is required. If ``POST['submit'] == "preview"`` or if there are |
||||
errors a preview template, ``comments/preview.html``, will be rendered. |
||||
""" |
||||
|
||||
# Require POST |
||||
if request.method != 'POST': |
||||
return http.HttpResponseNotAllowed(["POST"]) |
||||
|
||||
# Fill out some initial data fields from an authenticated user, if present |
||||
data = request.POST.copy() |
||||
if request.user.is_authenticated(): |
||||
if not data.get('name', ''): |
||||
data["name"] = request.user.get_full_name() |
||||
if not data.get('email', ''): |
||||
data["email"] = request.user.email |
||||
|
||||
# Look up the object we're trying to comment about |
||||
ctype = data.get("content_type") |
||||
object_pk = data.get("object_pk") |
||||
if ctype is None or object_pk is None: |
||||
return CommentPostBadRequest("Missing content_type or object_pk field.") |
||||
try: |
||||
model = models.get_model(*ctype.split(".", 1)) |
||||
target = model._default_manager.get(pk=object_pk) |
||||
except TypeError: |
||||
return CommentPostBadRequest( |
||||
"Invalid content_type value: %r" % escape(ctype)) |
||||
except AttributeError: |
||||
return CommentPostBadRequest( |
||||
"The given content-type %r does not resolve to a valid model." % \ |
||||
escape(ctype)) |
||||
except ObjectDoesNotExist: |
||||
return CommentPostBadRequest( |
||||
"No object matching content-type %r and object PK %r exists." % \ |
||||
(escape(ctype), escape(object_pk))) |
||||
|
||||
# Do we want to preview the comment? |
||||
preview = data.get("submit", "").lower() == "preview" or \ |
||||
data.get("preview", None) is not None |
||||
|
||||
# Construct the comment form |
||||
form = comments.get_form()(target, data=data) |
||||
|
||||
# Check security information |
||||
if form.security_errors(): |
||||
return CommentPostBadRequest( |
||||
"The comment form failed security verification: %s" % \ |
||||
escape(str(form.security_errors()))) |
||||
|
||||
# If there are errors or if we requested a preview show the comment |
||||
if form.errors or preview: |
||||
template_list = [ |
||||
"comments/%s_%s_preview.html" % tuple(str(model._meta).split(".")), |
||||
"comments/%s_preview.html" % model._meta.app_label, |
||||
"comments/preview.html", |
||||
] |
||||
return render_to_response( |
||||
template_list, { |
||||
"comment" : form.data.get("comment", ""), |
||||
"form" : form, |
||||
}, |
||||
RequestContext(request, {}) |
||||
) |
||||
|
||||
# Otherwise create the comment |
||||
comment = form.get_comment_object() |
||||
comment.ip_address = request.META.get("REMOTE_ADDR", None) |
||||
if request.user.is_authenticated(): |
||||
comment.user = request.user |
||||
|
||||
# Signal that the comment is about to be saved |
||||
responses = signals.comment_will_be_posted.send( |
||||
sender = comment.__class__, |
||||
comment = comment, |
||||
request = request |
||||
) |
||||
|
||||
for (receiver, response) in responses: |
||||
if response == False: |
||||
return CommentPostBadRequest( |
||||
"comment_will_be_posted receiver %r killed the comment" % receiver.__name__) |
||||
|
||||
# Save the comment and signal that it was saved |
||||
comment.save() |
||||
signals.comment_was_posted.send( |
||||
sender = comment.__class__, |
||||
comment = comment, |
||||
request = request |
||||
) |
||||
|
||||
return next_redirect(data, next, comment_done, c=comment._get_pk_val()) |
||||
|
||||
comment_done = confirmation_view( |
||||
template = "comments/posted.html", |
||||
doc = """Display a "comment was posted" success page.""" |
||||
) |
||||
|
||||
@ -0,0 +1,204 @@
@@ -0,0 +1,204 @@
|
||||
from django import template |
||||
from django.conf import settings |
||||
from django.shortcuts import get_object_or_404, render_to_response |
||||
from django.contrib.auth.decorators import login_required, permission_required |
||||
from utils import next_redirect, confirmation_view |
||||
from django.core.paginator import Paginator, InvalidPage |
||||
from django.http import Http404 |
||||
from django.contrib import comments |
||||
from django.contrib.comments import signals |
||||
|
||||
#@login_required |
||||
def flag(request, comment_id, next=None): |
||||
""" |
||||
Flags a comment. Confirmation on GET, action on POST. |
||||
|
||||
Templates: `comments/flag.html`, |
||||
Context: |
||||
comment |
||||
the flagged `comments.comment` object |
||||
""" |
||||
comment = get_object_or_404(comments.get_model(), pk=comment_id, site__pk=settings.SITE_ID) |
||||
|
||||
# Flag on POST |
||||
if request.method == 'POST': |
||||
flag, created = comments.models.CommentFlag.objects.get_or_create( |
||||
comment = comment, |
||||
user = request.user, |
||||
flag = comments.models.CommentFlag.SUGGEST_REMOVAL |
||||
) |
||||
signals.comment_was_flagged.send( |
||||
sender = comment.__class__, |
||||
comment = comment, |
||||
flag = flag, |
||||
created = created, |
||||
request = request, |
||||
) |
||||
return next_redirect(request.POST.copy(), next, flag_done, c=comment.pk) |
||||
|
||||
# Render a form on GET |
||||
else: |
||||
return render_to_response('comments/flag.html', |
||||
{'comment': comment, "next": next}, |
||||
template.RequestContext(request) |
||||
) |
||||
flag = login_required(flag) |
||||
|
||||
#@permission_required("comments.delete_comment") |
||||
def delete(request, comment_id, next=None): |
||||
""" |
||||
Deletes a comment. Confirmation on GET, action on POST. Requires the "can |
||||
moderate comments" permission. |
||||
|
||||
Templates: `comments/delete.html`, |
||||
Context: |
||||
comment |
||||
the flagged `comments.comment` object |
||||
""" |
||||
comment = get_object_or_404(comments.get_model(), pk=comment_id, site__pk=settings.SITE_ID) |
||||
|
||||
# Delete on POST |
||||
if request.method == 'POST': |
||||
# Flag the comment as deleted instead of actually deleting it. |
||||
flag, created = comments.models.CommentFlag.objects.get_or_create( |
||||
comment = comment, |
||||
user = request.user, |
||||
flag = comments.models.CommentFlag.MODERATOR_DELETION |
||||
) |
||||
comment.is_removed = True |
||||
comment.save() |
||||
signals.comment_was_flagged.send( |
||||
sender = comment.__class__, |
||||
comment = comment, |
||||
flag = flag, |
||||
created = created, |
||||
request = request, |
||||
) |
||||
return next_redirect(request.POST.copy(), next, delete_done, c=comment.pk) |
||||
|
||||
# Render a form on GET |
||||
else: |
||||
return render_to_response('comments/delete.html', |
||||
{'comment': comment, "next": next}, |
||||
template.RequestContext(request) |
||||
) |
||||
delete = permission_required("comments.can_moderate")(delete) |
||||
|
||||
#@permission_required("comments.can_moderate") |
||||
def approve(request, comment_id, next=None): |
||||
""" |
||||
Approve a comment (that is, mark it as public and non-removed). Confirmation |
||||
on GET, action on POST. Requires the "can moderate comments" permission. |
||||
|
||||
Templates: `comments/approve.html`, |
||||
Context: |
||||
comment |
||||
the `comments.comment` object for approval |
||||
""" |
||||
comment = get_object_or_404(comments.get_model(), pk=comment_id, site__pk=settings.SITE_ID) |
||||
|
||||
# Delete on POST |
||||
if request.method == 'POST': |
||||
# Flag the comment as approved. |
||||
flag, created = comments.models.CommentFlag.objects.get_or_create( |
||||
comment = comment, |
||||
user = request.user, |
||||
flag = comments.models.CommentFlag.MODERATOR_APPROVAL, |
||||
) |
||||
|
||||
comment.is_removed = False |
||||
comment.is_public = True |
||||
comment.save() |
||||
|
||||
signals.comment_was_flagged.send( |
||||
sender = comment.__class__, |
||||
comment = comment, |
||||
flag = flag, |
||||
created = created, |
||||
request = request, |
||||
) |
||||
return next_redirect(request.POST.copy(), next, approve_done, c=comment.pk) |
||||
|
||||
# Render a form on GET |
||||
else: |
||||
return render_to_response('comments/approve.html', |
||||
{'comment': comment, "next": next}, |
||||
template.RequestContext(request) |
||||
) |
||||
|
||||
approve = permission_required("comments.can_moderate")(approve) |
||||
|
||||
|
||||
#@permission_required("comments.can_moderate") |
||||
def moderation_queue(request): |
||||
""" |
||||
Displays a list of unapproved comments to be approved. |
||||
|
||||
Templates: `comments/moderation_queue.html` |
||||
Context: |
||||
comments |
||||
Comments to be approved (paginated). |
||||
empty |
||||
Is the comment list empty? |
||||
is_paginated |
||||
Is there more than one page? |
||||
results_per_page |
||||
Number of comments per page |
||||
has_next |
||||
Is there a next page? |
||||
has_previous |
||||
Is there a previous page? |
||||
page |
||||
The current page number |
||||
next |
||||
The next page number |
||||
pages |
||||
Number of pages |
||||
hits |
||||
Total number of comments |
||||
page_range |
||||
Range of page numbers |
||||
|
||||
""" |
||||
qs = comments.get_model().objects.filter(is_public=False, is_removed=False) |
||||
paginator = Paginator(qs, 100) |
||||
|
||||
try: |
||||
page = int(request.GET.get("page", 1)) |
||||
except ValueError: |
||||
raise Http404 |
||||
|
||||
try: |
||||
comments_per_page = paginator.page(page) |
||||
except InvalidPage: |
||||
raise Http404 |
||||
|
||||
return render_to_response("comments/moderation_queue.html", { |
||||
'comments' : comments_per_page.object_list, |
||||
'empty' : page == 1 and paginator.count == 0, |
||||
'is_paginated': paginator.num_pages > 1, |
||||
'results_per_page': 100, |
||||
'has_next': comments_per_page.has_next(), |
||||
'has_previous': comments_per_page.has_previous(), |
||||
'page': page, |
||||
'next': page + 1, |
||||
'previous': page - 1, |
||||
'pages': paginator.num_pages, |
||||
'hits' : paginator.count, |
||||
'page_range' : paginator.page_range |
||||
}, context_instance=template.RequestContext(request)) |
||||
|
||||
moderation_queue = permission_required("comments.can_moderate")(moderation_queue) |
||||
|
||||
flag_done = confirmation_view( |
||||
template = "comments/flagged.html", |
||||
doc = 'Displays a "comment was flagged" success page.' |
||||
) |
||||
delete_done = confirmation_view( |
||||
template = "comments/deleted.html", |
||||
doc = 'Displays a "comment was deleted" success page.' |
||||
) |
||||
approve_done = confirmation_view( |
||||
template = "comments/approved.html", |
||||
doc = 'Displays a "comment was approved" success page.' |
||||
) |
||||
@ -0,0 +1,58 @@
@@ -0,0 +1,58 @@
|
||||
""" |
||||
A few bits of helper functions for comment views. |
||||
""" |
||||
|
||||
import urllib |
||||
import textwrap |
||||
from django.http import HttpResponseRedirect |
||||
from django.core import urlresolvers |
||||
from django.shortcuts import render_to_response |
||||
from django.template import RequestContext |
||||
from django.core.exceptions import ObjectDoesNotExist |
||||
from django.conf import settings |
||||
from django.contrib import comments |
||||
|
||||
def next_redirect(data, default, default_view, **get_kwargs): |
||||
""" |
||||
Handle the "where should I go next?" part of comment views. |
||||
|
||||
The next value could be a kwarg to the function (``default``), or a |
||||
``?next=...`` GET arg, or the URL of a given view (``default_view``). See |
||||
the view modules for examples. |
||||
|
||||
Returns an ``HttpResponseRedirect``. |
||||
""" |
||||
next = data.get("next", default) |
||||
if next is None: |
||||
next = urlresolvers.reverse(default_view) |
||||
if get_kwargs: |
||||
next += "?" + urllib.urlencode(get_kwargs) |
||||
return HttpResponseRedirect(next) |
||||
|
||||
def confirmation_view(template, doc="Display a confirmation view."): |
||||
""" |
||||
Confirmation view generator for the "comment was |
||||
posted/flagged/deleted/approved" views. |
||||
""" |
||||
def confirmed(request): |
||||
comment = None |
||||
if 'c' in request.GET: |
||||
try: |
||||
comment = comments.get_model().objects.get(pk=request.GET['c']) |
||||
except ObjectDoesNotExist: |
||||
pass |
||||
return render_to_response(template, |
||||
{'comment': comment}, |
||||
context_instance=RequestContext(request) |
||||
) |
||||
|
||||
confirmed.__doc__ = textwrap.dedent("""\ |
||||
%s |
||||
|
||||
Templates: `%s`` |
||||
Context: |
||||
comment |
||||
The posted comment |
||||
""" % (doc, template) |
||||
) |
||||
return confirmed |
||||
@ -0,0 +1,387 @@
@@ -0,0 +1,387 @@
|
||||
""" |
||||
Classes allowing "generic" relations through ContentType and object-id fields. |
||||
""" |
||||
|
||||
from django.core.exceptions import ObjectDoesNotExist |
||||
from django.db import connection |
||||
from django.db.models import signals |
||||
from django.db import models |
||||
from django.db.models.fields.related import RelatedField, Field, ManyToManyRel |
||||
from django.db.models.loading import get_model |
||||
from django.forms import ModelForm |
||||
from django.forms.models import BaseModelFormSet, modelformset_factory, save_instance |
||||
from django.contrib.admin.options import InlineModelAdmin, flatten_fieldsets |
||||
from django.utils.encoding import smart_unicode |
||||
|
||||
class GenericForeignKey(object): |
||||
""" |
||||
Provides a generic relation to any object through content-type/object-id |
||||
fields. |
||||
""" |
||||
|
||||
def __init__(self, ct_field="content_type", fk_field="object_id"): |
||||
self.ct_field = ct_field |
||||
self.fk_field = fk_field |
||||
|
||||
def contribute_to_class(self, cls, name): |
||||
self.name = name |
||||
self.model = cls |
||||
self.cache_attr = "_%s_cache" % name |
||||
cls._meta.add_virtual_field(self) |
||||
|
||||
# For some reason I don't totally understand, using weakrefs here doesn't work. |
||||
signals.pre_init.connect(self.instance_pre_init, sender=cls, weak=False) |
||||
|
||||
# Connect myself as the descriptor for this field |
||||
setattr(cls, name, self) |
||||
|
||||
def instance_pre_init(self, signal, sender, args, kwargs, **_kwargs): |
||||
""" |
||||
Handles initializing an object with the generic FK instaed of |
||||
content-type/object-id fields. |
||||
""" |
||||
if self.name in kwargs: |
||||
value = kwargs.pop(self.name) |
||||
kwargs[self.ct_field] = self.get_content_type(obj=value) |
||||
kwargs[self.fk_field] = value._get_pk_val() |
||||
|
||||
def get_content_type(self, obj=None, id=None): |
||||
# Convenience function using get_model avoids a circular import when |
||||
# using this model |
||||
ContentType = get_model("contenttypes", "contenttype") |
||||
if obj: |
||||
return ContentType.objects.get_for_model(obj) |
||||
elif id: |
||||
return ContentType.objects.get_for_id(id) |
||||
else: |
||||
# This should never happen. I love comments like this, don't you? |
||||
raise Exception("Impossible arguments to GFK.get_content_type!") |
||||
|
||||
def __get__(self, instance, instance_type=None): |
||||
if instance is None: |
||||
raise AttributeError, u"%s must be accessed via instance" % self.name |
||||
|
||||
try: |
||||
return getattr(instance, self.cache_attr) |
||||
except AttributeError: |
||||
rel_obj = None |
||||
|
||||
# Make sure to use ContentType.objects.get_for_id() to ensure that |
||||
# lookups are cached (see ticket #5570). This takes more code than |
||||
# the naive ``getattr(instance, self.ct_field)``, but has better |
||||
# performance when dealing with GFKs in loops and such. |
||||
f = self.model._meta.get_field(self.ct_field) |
||||
ct_id = getattr(instance, f.get_attname(), None) |
||||
if ct_id: |
||||
ct = self.get_content_type(id=ct_id) |
||||
try: |
||||
rel_obj = ct.get_object_for_this_type(pk=getattr(instance, self.fk_field)) |
||||
except ObjectDoesNotExist: |
||||
pass |
||||
setattr(instance, self.cache_attr, rel_obj) |
||||
return rel_obj |
||||
|
||||
def __set__(self, instance, value): |
||||
if instance is None: |
||||
raise AttributeError, u"%s must be accessed via instance" % self.related.opts.object_name |
||||
|
||||
ct = None |
||||
fk = None |
||||
if value is not None: |
||||
ct = self.get_content_type(obj=value) |
||||
fk = value._get_pk_val() |
||||
|
||||
setattr(instance, self.ct_field, ct) |
||||
setattr(instance, self.fk_field, fk) |
||||
setattr(instance, self.cache_attr, value) |
||||
|
||||
class GenericRelation(RelatedField, Field): |
||||
"""Provides an accessor to generic related objects (e.g. comments)""" |
||||
|
||||
def __init__(self, to, **kwargs): |
||||
kwargs['verbose_name'] = kwargs.get('verbose_name', None) |
||||
kwargs['rel'] = GenericRel(to, |
||||
related_name=kwargs.pop('related_name', None), |
||||
limit_choices_to=kwargs.pop('limit_choices_to', None), |
||||
symmetrical=kwargs.pop('symmetrical', True)) |
||||
|
||||
# By its very nature, a GenericRelation doesn't create a table. |
||||
self.creates_table = False |
||||
|
||||
# Override content-type/object-id field names on the related class |
||||
self.object_id_field_name = kwargs.pop("object_id_field", "object_id") |
||||
self.content_type_field_name = kwargs.pop("content_type_field", "content_type") |
||||
|
||||
kwargs['blank'] = True |
||||
kwargs['editable'] = False |
||||
kwargs['serialize'] = False |
||||
Field.__init__(self, **kwargs) |
||||
|
||||
def get_choices_default(self): |
||||
return Field.get_choices(self, include_blank=False) |
||||
|
||||
def value_to_string(self, obj): |
||||
qs = getattr(obj, self.name).all() |
||||
return smart_unicode([instance._get_pk_val() for instance in qs]) |
||||
|
||||
def m2m_db_table(self): |
||||
return self.rel.to._meta.db_table |
||||
|
||||
def m2m_column_name(self): |
||||
return self.object_id_field_name |
||||
|
||||
def m2m_reverse_name(self): |
||||
return self.model._meta.pk.column |
||||
|
||||
def contribute_to_class(self, cls, name): |
||||
super(GenericRelation, self).contribute_to_class(cls, name) |
||||
|
||||
# Save a reference to which model this class is on for future use |
||||
self.model = cls |
||||
|
||||
# Add the descriptor for the m2m relation |
||||
setattr(cls, self.name, ReverseGenericRelatedObjectsDescriptor(self)) |
||||
|
||||
def contribute_to_related_class(self, cls, related): |
||||
pass |
||||
|
||||
def set_attributes_from_rel(self): |
||||
pass |
||||
|
||||
def get_internal_type(self): |
||||
return "ManyToManyField" |
||||
|
||||
def db_type(self): |
||||
# Since we're simulating a ManyToManyField, in effect, best return the |
||||
# same db_type as well. |
||||
return None |
||||
|
||||
def extra_filters(self, pieces, pos, negate): |
||||
""" |
||||
Return an extra filter to the queryset so that the results are filtered |
||||
on the appropriate content type. |
||||
""" |
||||
if negate: |
||||
return [] |
||||
ContentType = get_model("contenttypes", "contenttype") |
||||
content_type = ContentType.objects.get_for_model(self.model) |
||||
prefix = "__".join(pieces[:pos + 1]) |
||||
return [("%s__%s" % (prefix, self.content_type_field_name), |
||||
content_type)] |
||||
|
||||
class ReverseGenericRelatedObjectsDescriptor(object): |
||||
""" |
||||
This class provides the functionality that makes the related-object |
||||
managers available as attributes on a model class, for fields that have |
||||
multiple "remote" values and have a GenericRelation defined in their model |
||||
(rather than having another model pointed *at* them). In the example |
||||
"article.publications", the publications attribute is a |
||||
ReverseGenericRelatedObjectsDescriptor instance. |
||||
""" |
||||
def __init__(self, field): |
||||
self.field = field |
||||
|
||||
def __get__(self, instance, instance_type=None): |
||||
if instance is None: |
||||
raise AttributeError, "Manager must be accessed via instance" |
||||
|
||||
# This import is done here to avoid circular import importing this module |
||||
from django.contrib.contenttypes.models import ContentType |
||||
|
||||
# Dynamically create a class that subclasses the related model's |
||||
# default manager. |
||||
rel_model = self.field.rel.to |
||||
superclass = rel_model._default_manager.__class__ |
||||
RelatedManager = create_generic_related_manager(superclass) |
||||
|
||||
qn = connection.ops.quote_name |
||||
|
||||
manager = RelatedManager( |
||||
model = rel_model, |
||||
instance = instance, |
||||
symmetrical = (self.field.rel.symmetrical and instance.__class__ == rel_model), |
||||
join_table = qn(self.field.m2m_db_table()), |
||||
source_col_name = qn(self.field.m2m_column_name()), |
||||
target_col_name = qn(self.field.m2m_reverse_name()), |
||||
content_type = ContentType.objects.get_for_model(self.field.model), |
||||
content_type_field_name = self.field.content_type_field_name, |
||||
object_id_field_name = self.field.object_id_field_name |
||||
) |
||||
|
||||
return manager |
||||
|
||||
def __set__(self, instance, value): |
||||
if instance is None: |
||||
raise AttributeError, "Manager must be accessed via instance" |
||||
|
||||
manager = self.__get__(instance) |
||||
manager.clear() |
||||
for obj in value: |
||||
manager.add(obj) |
||||
|
||||
def create_generic_related_manager(superclass): |
||||
""" |
||||
Factory function for a manager that subclasses 'superclass' (which is a |
||||
Manager) and adds behavior for generic related objects. |
||||
""" |
||||
|
||||
class GenericRelatedObjectManager(superclass): |
||||
def __init__(self, model=None, core_filters=None, instance=None, symmetrical=None, |
||||
join_table=None, source_col_name=None, target_col_name=None, content_type=None, |
||||
content_type_field_name=None, object_id_field_name=None): |
||||
|
||||
super(GenericRelatedObjectManager, self).__init__() |
||||
self.core_filters = core_filters or {} |
||||
self.model = model |
||||
self.content_type = content_type |
||||
self.symmetrical = symmetrical |
||||
self.instance = instance |
||||
self.join_table = join_table |
||||
self.join_table = model._meta.db_table |
||||
self.source_col_name = source_col_name |
||||
self.target_col_name = target_col_name |
||||
self.content_type_field_name = content_type_field_name |
||||
self.object_id_field_name = object_id_field_name |
||||
self.pk_val = self.instance._get_pk_val() |
||||
|
||||
def get_query_set(self): |
||||
query = { |
||||
'%s__pk' % self.content_type_field_name : self.content_type.id, |
||||
'%s__exact' % self.object_id_field_name : self.pk_val, |
||||
} |
||||
return superclass.get_query_set(self).filter(**query) |
||||
|
||||
def add(self, *objs): |
||||
for obj in objs: |
||||
setattr(obj, self.content_type_field_name, self.content_type) |
||||
setattr(obj, self.object_id_field_name, self.pk_val) |
||||
obj.save() |
||||
add.alters_data = True |
||||
|
||||
def remove(self, *objs): |
||||
for obj in objs: |
||||
obj.delete() |
||||
remove.alters_data = True |
||||
|
||||
def clear(self): |
||||
for obj in self.all(): |
||||
obj.delete() |
||||
clear.alters_data = True |
||||
|
||||
def create(self, **kwargs): |
||||
kwargs[self.content_type_field_name] = self.content_type |
||||
kwargs[self.object_id_field_name] = self.pk_val |
||||
return super(GenericRelatedObjectManager, self).create(**kwargs) |
||||
create.alters_data = True |
||||
|
||||
return GenericRelatedObjectManager |
||||
|
||||
class GenericRel(ManyToManyRel): |
||||
def __init__(self, to, related_name=None, limit_choices_to=None, symmetrical=True): |
||||
self.to = to |
||||
self.related_name = related_name |
||||
self.limit_choices_to = limit_choices_to or {} |
||||
self.symmetrical = symmetrical |
||||
self.multiple = True |
||||
|
||||
class BaseGenericInlineFormSet(BaseModelFormSet): |
||||
""" |
||||
A formset for generic inline objects to a parent. |
||||
""" |
||||
ct_field_name = "content_type" |
||||
ct_fk_field_name = "object_id" |
||||
|
||||
def __init__(self, data=None, files=None, instance=None, save_as_new=None): |
||||
opts = self.model._meta |
||||
self.instance = instance |
||||
self.rel_name = '-'.join(( |
||||
opts.app_label, opts.object_name.lower(), |
||||
self.ct_field.name, self.ct_fk_field.name, |
||||
)) |
||||
super(BaseGenericInlineFormSet, self).__init__( |
||||
queryset=self.get_queryset(), data=data, files=files, |
||||
prefix=self.rel_name |
||||
) |
||||
|
||||
def get_queryset(self): |
||||
# Avoid a circular import. |
||||
from django.contrib.contenttypes.models import ContentType |
||||
if self.instance is None: |
||||
return self.model._default_manager.empty() |
||||
return self.model._default_manager.filter(**{ |
||||
self.ct_field.name: ContentType.objects.get_for_model(self.instance), |
||||
self.ct_fk_field.name: self.instance.pk, |
||||
}) |
||||
|
||||
def save_new(self, form, commit=True): |
||||
# Avoid a circular import. |
||||
from django.contrib.contenttypes.models import ContentType |
||||
kwargs = { |
||||
self.ct_field.get_attname(): ContentType.objects.get_for_model(self.instance).pk, |
||||
self.ct_fk_field.get_attname(): self.instance.pk, |
||||
} |
||||
new_obj = self.model(**kwargs) |
||||
return save_instance(form, new_obj, commit=commit) |
||||
|
||||
def generic_inlineformset_factory(model, form=ModelForm, |
||||
formset=BaseGenericInlineFormSet, |
||||
ct_field="content_type", fk_field="object_id", |
||||
fields=None, exclude=None, |
||||
extra=3, can_order=False, can_delete=True, |
||||
max_num=0, |
||||
formfield_callback=lambda f: f.formfield()): |
||||
""" |
||||
Returns an ``GenericInlineFormSet`` for the given kwargs. |
||||
|
||||
You must provide ``ct_field`` and ``object_id`` if they different from the |
||||
defaults ``content_type`` and ``object_id`` respectively. |
||||
""" |
||||
opts = model._meta |
||||
# Avoid a circular import. |
||||
from django.contrib.contenttypes.models import ContentType |
||||
# if there is no field called `ct_field` let the exception propagate |
||||
ct_field = opts.get_field(ct_field) |
||||
if not isinstance(ct_field, models.ForeignKey) or ct_field.rel.to != ContentType: |
||||
raise Exception("fk_name '%s' is not a ForeignKey to ContentType" % ct_field) |
||||
fk_field = opts.get_field(fk_field) # let the exception propagate |
||||
if exclude is not None: |
||||
exclude.extend([ct_field.name, fk_field.name]) |
||||
else: |
||||
exclude = [ct_field.name, fk_field.name] |
||||
FormSet = modelformset_factory(model, form=form, |
||||
formfield_callback=formfield_callback, |
||||
formset=formset, |
||||
extra=extra, can_delete=can_delete, can_order=can_order, |
||||
fields=fields, exclude=exclude, max_num=max_num) |
||||
FormSet.ct_field = ct_field |
||||
FormSet.ct_fk_field = fk_field |
||||
return FormSet |
||||
|
||||
class GenericInlineModelAdmin(InlineModelAdmin): |
||||
ct_field = "content_type" |
||||
ct_fk_field = "object_id" |
||||
formset = BaseGenericInlineFormSet |
||||
|
||||
def get_formset(self, request, obj=None): |
||||
if self.declared_fieldsets: |
||||
fields = flatten_fieldsets(self.declared_fieldsets) |
||||
else: |
||||
fields = None |
||||
defaults = { |
||||
"ct_field": self.ct_field, |
||||
"fk_field": self.ct_fk_field, |
||||
"form": self.form, |
||||
"formfield_callback": self.formfield_for_dbfield, |
||||
"formset": self.formset, |
||||
"extra": self.extra, |
||||
"can_delete": True, |
||||
"can_order": False, |
||||
"fields": fields, |
||||
} |
||||
return generic_inlineformset_factory(self.model, **defaults) |
||||
|
||||
class GenericStackedInline(GenericInlineModelAdmin): |
||||
template = 'admin/edit_inline/stacked.html' |
||||
|
||||
class GenericTabularInline(GenericInlineModelAdmin): |
||||
template = 'admin/edit_inline/tabular.html' |
||||
@ -0,0 +1,42 @@
@@ -0,0 +1,42 @@
|
||||
from django.contrib.contenttypes.models import ContentType |
||||
from django.db.models import get_apps, get_models, signals |
||||
from django.utils.encoding import smart_unicode |
||||
|
||||
def update_contenttypes(app, created_models, verbosity=2, **kwargs): |
||||
""" |
||||
Creates content types for models in the given app, removing any model |
||||
entries that no longer have a matching model class. |
||||
""" |
||||
ContentType.objects.clear_cache() |
||||
content_types = list(ContentType.objects.filter(app_label=app.__name__.split('.')[-2])) |
||||
app_models = get_models(app) |
||||
if not app_models: |
||||
return |
||||
for klass in app_models: |
||||
opts = klass._meta |
||||
try: |
||||
ct = ContentType.objects.get(app_label=opts.app_label, |
||||
model=opts.object_name.lower()) |
||||
content_types.remove(ct) |
||||
except ContentType.DoesNotExist: |
||||
ct = ContentType(name=smart_unicode(opts.verbose_name_raw), |
||||
app_label=opts.app_label, model=opts.object_name.lower()) |
||||
ct.save() |
||||
if verbosity >= 2: |
||||
print "Adding content type '%s | %s'" % (ct.app_label, ct.model) |
||||
# The presence of any remaining content types means the supplied app has an |
||||
# undefined model and can safely be removed, which cascades to also remove |
||||
# related permissions. |
||||
for ct in content_types: |
||||
if verbosity >= 2: |
||||
print "Deleting stale content type '%s | %s'" % (ct.app_label, ct.model) |
||||
ct.delete() |
||||
|
||||
def update_all_contenttypes(verbosity=2): |
||||
for app in get_apps(): |
||||
update_contenttypes(app, None, verbosity) |
||||
|
||||
signals.post_syncdb.connect(update_contenttypes) |
||||
|
||||
if __name__ == "__main__": |
||||
update_all_contenttypes() |
||||
@ -0,0 +1,92 @@
@@ -0,0 +1,92 @@
|
||||
from django.db import models |
||||
from django.utils.translation import ugettext_lazy as _ |
||||
from django.utils.encoding import smart_unicode |
||||
|
||||
class ContentTypeManager(models.Manager): |
||||
|
||||
# Cache to avoid re-looking up ContentType objects all over the place. |
||||
# This cache is shared by all the get_for_* methods. |
||||
_cache = {} |
||||
|
||||
def get_for_model(self, model): |
||||
""" |
||||
Returns the ContentType object for a given model, creating the |
||||
ContentType if necessary. Lookups are cached so that subsequent lookups |
||||
for the same model don't hit the database. |
||||
""" |
||||
opts = model._meta |
||||
key = (opts.app_label, opts.object_name.lower()) |
||||
try: |
||||
ct = self.__class__._cache[key] |
||||
except KeyError: |
||||
# Load or create the ContentType entry. The smart_unicode() is |
||||
# needed around opts.verbose_name_raw because name_raw might be a |
||||
# django.utils.functional.__proxy__ object. |
||||
ct, created = self.get_or_create( |
||||
app_label = opts.app_label, |
||||
model = opts.object_name.lower(), |
||||
defaults = {'name': smart_unicode(opts.verbose_name_raw)}, |
||||
) |
||||
self._add_to_cache(ct) |
||||
|
||||
return ct |
||||
|
||||
def get_for_id(self, id): |
||||
""" |
||||
Lookup a ContentType by ID. Uses the same shared cache as get_for_model |
||||
(though ContentTypes are obviously not created on-the-fly by get_by_id). |
||||
""" |
||||
try: |
||||
ct = self.__class__._cache[id] |
||||
except KeyError: |
||||
# This could raise a DoesNotExist; that's correct behavior and will |
||||
# make sure that only correct ctypes get stored in the cache dict. |
||||
ct = self.get(pk=id) |
||||
self._add_to_cache(ct) |
||||
return ct |
||||
|
||||
def clear_cache(self): |
||||
""" |
||||
Clear out the content-type cache. This needs to happen during database |
||||
flushes to prevent caching of "stale" content type IDs (see |
||||
django.contrib.contenttypes.management.update_contenttypes for where |
||||
this gets called). |
||||
""" |
||||
self.__class__._cache.clear() |
||||
|
||||
def _add_to_cache(self, ct): |
||||
"""Insert a ContentType into the cache.""" |
||||
model = ct.model_class() |
||||
key = (model._meta.app_label, model._meta.object_name.lower()) |
||||
self.__class__._cache[key] = ct |
||||
self.__class__._cache[ct.id] = ct |
||||
|
||||
class ContentType(models.Model): |
||||
name = models.CharField(max_length=100) |
||||
app_label = models.CharField(max_length=100) |
||||
model = models.CharField(_('python model class name'), max_length=100) |
||||
objects = ContentTypeManager() |
||||
|
||||
class Meta: |
||||
verbose_name = _('content type') |
||||
verbose_name_plural = _('content types') |
||||
db_table = 'django_content_type' |
||||
ordering = ('name',) |
||||
unique_together = (('app_label', 'model'),) |
||||
|
||||
def __unicode__(self): |
||||
return self.name |
||||
|
||||
def model_class(self): |
||||
"Returns the Python model class for this type of content." |
||||
from django.db import models |
||||
return models.get_model(self.app_label, self.model) |
||||
|
||||
def get_object_for_this_type(self, **kwargs): |
||||
""" |
||||
Returns an object of this type for the keyword arguments given. |
||||
Basically, this is a proxy around this object_type's get_object() model |
||||
method. The ObjectNotExist exception, if thrown, will not be caught, |
||||
so code that calls this method should catch it. |
||||
""" |
||||
return self.model_class()._default_manager.get(**kwargs) |
||||
@ -0,0 +1,47 @@
@@ -0,0 +1,47 @@
|
||||
""" |
||||
Make sure that the content type cache (see ContentTypeManager) works correctly. |
||||
Lookups for a particular content type -- by model or by ID -- should hit the |
||||
database only on the first lookup. |
||||
|
||||
First, let's make sure we're dealing with a blank slate (and that DEBUG is on so |
||||
that queries get logged):: |
||||
|
||||
>>> from django.conf import settings |
||||
>>> settings.DEBUG = True |
||||
|
||||
>>> from django.contrib.contenttypes.models import ContentType |
||||
>>> ContentType.objects.clear_cache() |
||||
|
||||
>>> from django import db |
||||
>>> db.reset_queries() |
||||
|
||||
At this point, a lookup for a ContentType should hit the DB:: |
||||
|
||||
>>> ContentType.objects.get_for_model(ContentType) |
||||
<ContentType: content type> |
||||
|
||||
>>> len(db.connection.queries) |
||||
1 |
||||
|
||||
A second hit, though, won't hit the DB, nor will a lookup by ID:: |
||||
|
||||
>>> ct = ContentType.objects.get_for_model(ContentType) |
||||
>>> len(db.connection.queries) |
||||
1 |
||||
>>> ContentType.objects.get_for_id(ct.id) |
||||
<ContentType: content type> |
||||
>>> len(db.connection.queries) |
||||
1 |
||||
|
||||
Once we clear the cache, another lookup will again hit the DB:: |
||||
|
||||
>>> ContentType.objects.clear_cache() |
||||
>>> ContentType.objects.get_for_model(ContentType) |
||||
<ContentType: content type> |
||||
>>> len(db.connection.queries) |
||||
2 |
||||
|
||||
Don't forget to reset DEBUG! |
||||
|
||||
>>> settings.DEBUG = False |
||||
""" |
||||
@ -0,0 +1,94 @@
@@ -0,0 +1,94 @@
|
||||
""" |
||||
Cross Site Request Forgery Middleware. |
||||
|
||||
This module provides a middleware that implements protection |
||||
against request forgeries from other sites. |
||||
""" |
||||
|
||||
import re |
||||
import itertools |
||||
|
||||
from django.conf import settings |
||||
from django.http import HttpResponseForbidden |
||||
from django.utils.hashcompat import md5_constructor |
||||
from django.utils.safestring import mark_safe |
||||
|
||||
_ERROR_MSG = mark_safe('<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"><body><h1>403 Forbidden</h1><p>Cross Site Request Forgery detected. Request aborted.</p></body></html>') |
||||
|
||||
_POST_FORM_RE = \ |
||||
re.compile(r'(<form\W[^>]*\bmethod=(\'|"|)POST(\'|"|)\b[^>]*>)', re.IGNORECASE) |
||||
|
||||
_HTML_TYPES = ('text/html', 'application/xhtml+xml') |
||||
|
||||
def _make_token(session_id): |
||||
return md5_constructor(settings.SECRET_KEY + session_id).hexdigest() |
||||
|
||||
class CsrfMiddleware(object): |
||||
"""Django middleware that adds protection against Cross Site |
||||
Request Forgeries by adding hidden form fields to POST forms and |
||||
checking requests for the correct value. |
||||
|
||||
In the list of middlewares, SessionMiddleware is required, and must come |
||||
after this middleware. CsrfMiddleWare must come after compression |
||||
middleware. |
||||
|
||||
If a session ID cookie is present, it is hashed with the SECRET_KEY |
||||
setting to create an authentication token. This token is added to all |
||||
outgoing POST forms and is expected on all incoming POST requests that |
||||
have a session ID cookie. |
||||
|
||||
If you are setting cookies directly, instead of using Django's session |
||||
framework, this middleware will not work. |
||||
""" |
||||
|
||||
def process_request(self, request): |
||||
if request.method == 'POST': |
||||
try: |
||||
session_id = request.COOKIES[settings.SESSION_COOKIE_NAME] |
||||
except KeyError: |
||||
# No session, no check required |
||||
return None |
||||
|
||||
csrf_token = _make_token(session_id) |
||||
# check incoming token |
||||
try: |
||||
request_csrf_token = request.POST['csrfmiddlewaretoken'] |
||||
except KeyError: |
||||
return HttpResponseForbidden(_ERROR_MSG) |
||||
|
||||
if request_csrf_token != csrf_token: |
||||
return HttpResponseForbidden(_ERROR_MSG) |
||||
|
||||
return None |
||||
|
||||
def process_response(self, request, response): |
||||
csrf_token = None |
||||
try: |
||||
cookie = response.cookies[settings.SESSION_COOKIE_NAME] |
||||
csrf_token = _make_token(cookie.value) |
||||
except KeyError: |
||||
# No outgoing cookie to set session, but |
||||
# a session might already exist. |
||||
try: |
||||
session_id = request.COOKIES[settings.SESSION_COOKIE_NAME] |
||||
csrf_token = _make_token(session_id) |
||||
except KeyError: |
||||
# no incoming or outgoing cookie |
||||
pass |
||||
|
||||
if csrf_token is not None and \ |
||||
response['Content-Type'].split(';')[0] in _HTML_TYPES: |
||||
|
||||
# ensure we don't add the 'id' attribute twice (HTML validity) |
||||
idattributes = itertools.chain(("id='csrfmiddlewaretoken'",), |
||||
itertools.repeat('')) |
||||
def add_csrf_field(match): |
||||
"""Returns the matched <form> tag plus the added <input> element""" |
||||
return mark_safe(match.group() + "<div style='display:none;'>" + \ |
||||
"<input type='hidden' " + idattributes.next() + \ |
||||
" name='csrfmiddlewaretoken' value='" + csrf_token + \ |
||||
"' /></div>") |
||||
|
||||
# Modify any POST forms |
||||
response.content = _POST_FORM_RE.sub(add_csrf_field, response.content) |
||||
return response |
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
from django.contrib.databrowse.sites import DatabrowsePlugin, ModelDatabrowse, DatabrowseSite, site |
||||
@ -0,0 +1,217 @@
@@ -0,0 +1,217 @@
|
||||
""" |
||||
These classes are light wrappers around Django's database API that provide |
||||
convenience functionality and permalink functions for the databrowse app. |
||||
""" |
||||
|
||||
from django.db import models |
||||
from django.utils import dateformat |
||||
from django.utils.text import capfirst |
||||
from django.utils.translation import get_date_formats |
||||
from django.utils.encoding import smart_unicode, smart_str, iri_to_uri |
||||
from django.utils.safestring import mark_safe |
||||
from django.db.models.query import QuerySet |
||||
|
||||
EMPTY_VALUE = '(None)' |
||||
DISPLAY_SIZE = 100 |
||||
|
||||
class EasyModel(object): |
||||
def __init__(self, site, model): |
||||
self.site = site |
||||
self.model = model |
||||
self.model_list = site.registry.keys() |
||||
self.verbose_name = model._meta.verbose_name |
||||
self.verbose_name_plural = model._meta.verbose_name_plural |
||||
|
||||
def __repr__(self): |
||||
return '<EasyModel for %s>' % smart_str(self.model._meta.object_name) |
||||
|
||||
def model_databrowse(self): |
||||
"Returns the ModelDatabrowse class for this model." |
||||
return self.site.registry[self.model] |
||||
|
||||
def url(self): |
||||
return mark_safe('%s%s/%s/' % (self.site.root_url, self.model._meta.app_label, self.model._meta.module_name)) |
||||
|
||||
def objects(self, **kwargs): |
||||
return self.get_query_set().filter(**kwargs) |
||||
|
||||
def get_query_set(self): |
||||
easy_qs = self.model._default_manager.get_query_set()._clone(klass=EasyQuerySet) |
||||
easy_qs._easymodel = self |
||||
return easy_qs |
||||
|
||||
def object_by_pk(self, pk): |
||||
return EasyInstance(self, self.model._default_manager.get(pk=pk)) |
||||
|
||||
def sample_objects(self): |
||||
for obj in self.model._default_manager.all()[:3]: |
||||
yield EasyInstance(self, obj) |
||||
|
||||
def field(self, name): |
||||
try: |
||||
f = self.model._meta.get_field(name) |
||||
except models.FieldDoesNotExist: |
||||
return None |
||||
return EasyField(self, f) |
||||
|
||||
def fields(self): |
||||
return [EasyField(self, f) for f in (self.model._meta.fields + self.model._meta.many_to_many)] |
||||
|
||||
class EasyField(object): |
||||
def __init__(self, easy_model, field): |
||||
self.model, self.field = easy_model, field |
||||
|
||||
def __repr__(self): |
||||
return smart_str(u'<EasyField for %s.%s>' % (self.model.model._meta.object_name, self.field.name)) |
||||
|
||||
def choices(self): |
||||
for value, label in self.field.choices: |
||||
yield EasyChoice(self.model, self, value, label) |
||||
|
||||
def url(self): |
||||
if self.field.choices: |
||||
return mark_safe('%s%s/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.name)) |
||||
elif self.field.rel: |
||||
return mark_safe('%s%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name)) |
||||
|
||||
class EasyChoice(object): |
||||
def __init__(self, easy_model, field, value, label): |
||||
self.model, self.field = easy_model, field |
||||
self.value, self.label = value, label |
||||
|
||||
def __repr__(self): |
||||
return smart_str(u'<EasyChoice for %s.%s>' % (self.model.model._meta.object_name, self.field.name)) |
||||
|
||||
def url(self): |
||||
return mark_safe('%s%s/%s/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.field.name, iri_to_uri(self.value))) |
||||
|
||||
class EasyInstance(object): |
||||
def __init__(self, easy_model, instance): |
||||
self.model, self.instance = easy_model, instance |
||||
|
||||
def __repr__(self): |
||||
return smart_str(u'<EasyInstance for %s (%s)>' % (self.model.model._meta.object_name, self.instance._get_pk_val())) |
||||
|
||||
def __unicode__(self): |
||||
val = smart_unicode(self.instance) |
||||
if len(val) > DISPLAY_SIZE: |
||||
return val[:DISPLAY_SIZE] + u'...' |
||||
return val |
||||
|
||||
def __str__(self): |
||||
return self.__unicode__().encode('utf-8') |
||||
|
||||
def pk(self): |
||||
return self.instance._get_pk_val() |
||||
|
||||
def url(self): |
||||
return mark_safe('%s%s/%s/objects/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, iri_to_uri(self.pk()))) |
||||
|
||||
def fields(self): |
||||
""" |
||||
Generator that yields EasyInstanceFields for each field in this |
||||
EasyInstance's model. |
||||
""" |
||||
for f in self.model.model._meta.fields + self.model.model._meta.many_to_many: |
||||
yield EasyInstanceField(self.model, self, f) |
||||
|
||||
def related_objects(self): |
||||
""" |
||||
Generator that yields dictionaries of all models that have this |
||||
EasyInstance's model as a ForeignKey or ManyToManyField, along with |
||||
lists of related objects. |
||||
""" |
||||
for rel_object in self.model.model._meta.get_all_related_objects() + self.model.model._meta.get_all_related_many_to_many_objects(): |
||||
if rel_object.model not in self.model.model_list: |
||||
continue # Skip models that aren't in the model_list |
||||
em = EasyModel(self.model.site, rel_object.model) |
||||
yield { |
||||
'model': em, |
||||
'related_field': rel_object.field.verbose_name, |
||||
'object_list': [EasyInstance(em, i) for i in getattr(self.instance, rel_object.get_accessor_name()).all()], |
||||
} |
||||
|
||||
class EasyInstanceField(object): |
||||
def __init__(self, easy_model, instance, field): |
||||
self.model, self.field, self.instance = easy_model, field, instance |
||||
self.raw_value = getattr(instance.instance, field.name) |
||||
|
||||
def __repr__(self): |
||||
return smart_str(u'<EasyInstanceField for %s.%s>' % (self.model.model._meta.object_name, self.field.name)) |
||||
|
||||
def values(self): |
||||
""" |
||||
Returns a list of values for this field for this instance. It's a list |
||||
so we can accomodate many-to-many fields. |
||||
""" |
||||
# This import is deliberately inside the function because it causes |
||||
# some settings to be imported, and we don't want to do that at the |
||||
# module level. |
||||
if self.field.rel: |
||||
if isinstance(self.field.rel, models.ManyToOneRel): |
||||
objs = getattr(self.instance.instance, self.field.name) |
||||
elif isinstance(self.field.rel, models.ManyToManyRel): # ManyToManyRel |
||||
return list(getattr(self.instance.instance, self.field.name).all()) |
||||
elif self.field.choices: |
||||
objs = dict(self.field.choices).get(self.raw_value, EMPTY_VALUE) |
||||
elif isinstance(self.field, models.DateField) or isinstance(self.field, models.TimeField): |
||||
if self.raw_value: |
||||
date_format, datetime_format, time_format = get_date_formats() |
||||
if isinstance(self.field, models.DateTimeField): |
||||
objs = capfirst(dateformat.format(self.raw_value, datetime_format)) |
||||
elif isinstance(self.field, models.TimeField): |
||||
objs = capfirst(dateformat.time_format(self.raw_value, time_format)) |
||||
else: |
||||
objs = capfirst(dateformat.format(self.raw_value, date_format)) |
||||
else: |
||||
objs = EMPTY_VALUE |
||||
elif isinstance(self.field, models.BooleanField) or isinstance(self.field, models.NullBooleanField): |
||||
objs = {True: 'Yes', False: 'No', None: 'Unknown'}[self.raw_value] |
||||
else: |
||||
objs = self.raw_value |
||||
return [objs] |
||||
|
||||
def urls(self): |
||||
"Returns a list of (value, URL) tuples." |
||||
# First, check the urls() method for each plugin. |
||||
plugin_urls = [] |
||||
for plugin_name, plugin in self.model.model_databrowse().plugins.items(): |
||||
urls = plugin.urls(plugin_name, self) |
||||
if urls is not None: |
||||
#plugin_urls.append(urls) |
||||
values = self.values() |
||||
return zip(self.values(), urls) |
||||
if self.field.rel: |
||||
m = EasyModel(self.model.site, self.field.rel.to) |
||||
if self.field.rel.to in self.model.model_list: |
||||
lst = [] |
||||
for value in self.values(): |
||||
url = mark_safe('%s%s/%s/objects/%s/' % (self.model.site.root_url, m.model._meta.app_label, m.model._meta.module_name, iri_to_uri(value._get_pk_val()))) |
||||
lst.append((smart_unicode(value), url)) |
||||
else: |
||||
lst = [(value, None) for value in self.values()] |
||||
elif self.field.choices: |
||||
lst = [] |
||||
for value in self.values(): |
||||
url = mark_safe('%s%s/%s/fields/%s/%s/' % (self.model.site.root_url, self.model.model._meta.app_label, self.model.model._meta.module_name, self.field.name, iri_to_uri(self.raw_value))) |
||||
lst.append((value, url)) |
||||
elif isinstance(self.field, models.URLField): |
||||
val = self.values()[0] |
||||
lst = [(val, iri_to_uri(val))] |
||||
else: |
||||
lst = [(self.values()[0], None)] |
||||
return lst |
||||
|
||||
class EasyQuerySet(QuerySet): |
||||
""" |
||||
When creating (or cloning to) an `EasyQuerySet`, make sure to set the |
||||
`_easymodel` variable to the related `EasyModel`. |
||||
""" |
||||
def iterator(self, *args, **kwargs): |
||||
for obj in super(EasyQuerySet, self).iterator(*args, **kwargs): |
||||
yield EasyInstance(self._easymodel, obj) |
||||
|
||||
def _clone(self, *args, **kwargs): |
||||
c = super(EasyQuerySet, self)._clone(*args, **kwargs) |
||||
c._easymodel = self._easymodel |
||||
return c |
||||
@ -0,0 +1,86 @@
@@ -0,0 +1,86 @@
|
||||
from django import http |
||||
from django.db import models |
||||
from django.contrib.databrowse.datastructures import EasyModel |
||||
from django.contrib.databrowse.sites import DatabrowsePlugin |
||||
from django.shortcuts import render_to_response |
||||
from django.utils.text import capfirst |
||||
from django.utils.encoding import force_unicode |
||||
from django.utils.safestring import mark_safe |
||||
from django.views.generic import date_based |
||||
from django.utils import datetime_safe |
||||
|
||||
class CalendarPlugin(DatabrowsePlugin): |
||||
def __init__(self, field_names=None): |
||||
self.field_names = field_names |
||||
|
||||
def field_dict(self, model): |
||||
""" |
||||
Helper function that returns a dictionary of all DateFields or |
||||
DateTimeFields in the given model. If self.field_names is set, it takes |
||||
take that into account when building the dictionary. |
||||
""" |
||||
if self.field_names is None: |
||||
return dict([(f.name, f) for f in model._meta.fields if isinstance(f, models.DateField)]) |
||||
else: |
||||
return dict([(f.name, f) for f in model._meta.fields if isinstance(f, models.DateField) and f.name in self.field_names]) |
||||
|
||||
def model_index_html(self, request, model, site): |
||||
fields = self.field_dict(model) |
||||
if not fields: |
||||
return u'' |
||||
return mark_safe(u'<p class="filter"><strong>View calendar by:</strong> %s</p>' % \ |
||||
u', '.join(['<a href="calendars/%s/">%s</a>' % (f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()])) |
||||
|
||||
def urls(self, plugin_name, easy_instance_field): |
||||
if isinstance(easy_instance_field.field, models.DateField): |
||||
d = easy_instance_field.raw_value |
||||
return [mark_safe(u'%s%s/%s/%s/%s/%s/' % ( |
||||
easy_instance_field.model.url(), |
||||
plugin_name, easy_instance_field.field.name, |
||||
d.year, |
||||
datetime_safe.new_date(d).strftime('%b').lower(), |
||||
d.day))] |
||||
|
||||
def model_view(self, request, model_databrowse, url): |
||||
self.model, self.site = model_databrowse.model, model_databrowse.site |
||||
self.fields = self.field_dict(self.model) |
||||
|
||||
# If the model has no DateFields, there's no point in going further. |
||||
if not self.fields: |
||||
raise http.Http404('The requested model has no calendars.') |
||||
|
||||
if url is None: |
||||
return self.homepage_view(request) |
||||
url_bits = url.split('/') |
||||
if self.fields.has_key(url_bits[0]): |
||||
return self.calendar_view(request, self.fields[url_bits[0]], *url_bits[1:]) |
||||
|
||||
raise http.Http404('The requested page does not exist.') |
||||
|
||||
def homepage_view(self, request): |
||||
easy_model = EasyModel(self.site, self.model) |
||||
field_list = self.fields.values() |
||||
field_list.sort(lambda x, y: cmp(x.verbose_name, y.verbose_name)) |
||||
return render_to_response('databrowse/calendar_homepage.html', {'root_url': self.site.root_url, 'model': easy_model, 'field_list': field_list}) |
||||
|
||||
def calendar_view(self, request, field, year=None, month=None, day=None): |
||||
easy_model = EasyModel(self.site, self.model) |
||||
queryset = easy_model.get_query_set() |
||||
extra_context = {'root_url': self.site.root_url, 'model': easy_model, 'field': field} |
||||
if day is not None: |
||||
return date_based.archive_day(request, year, month, day, queryset, field.name, |
||||
template_name='databrowse/calendar_day.html', allow_empty=False, allow_future=True, |
||||
extra_context=extra_context) |
||||
elif month is not None: |
||||
return date_based.archive_month(request, year, month, queryset, field.name, |
||||
template_name='databrowse/calendar_month.html', allow_empty=False, allow_future=True, |
||||
extra_context=extra_context) |
||||
elif year is not None: |
||||
return date_based.archive_year(request, year, queryset, field.name, |
||||
template_name='databrowse/calendar_year.html', allow_empty=False, allow_future=True, |
||||
extra_context=extra_context) |
||||
else: |
||||
return date_based.archive_index(request, queryset, field.name, |
||||
template_name='databrowse/calendar_main.html', allow_empty=True, allow_future=True, |
||||
extra_context=extra_context) |
||||
assert False, ('%s, %s, %s, %s' % (field, year, month, day)) |
||||
@ -0,0 +1,74 @@
@@ -0,0 +1,74 @@
|
||||
from django import http |
||||
from django.db import models |
||||
from django.contrib.databrowse.datastructures import EasyModel |
||||
from django.contrib.databrowse.sites import DatabrowsePlugin |
||||
from django.shortcuts import render_to_response |
||||
from django.utils.text import capfirst |
||||
from django.utils.encoding import smart_str, force_unicode |
||||
from django.utils.safestring import mark_safe |
||||
import urllib |
||||
|
||||
class FieldChoicePlugin(DatabrowsePlugin): |
||||
def __init__(self, field_filter=None): |
||||
# If field_filter is given, it should be a callable that takes a |
||||
# Django database Field instance and returns True if that field should |
||||
# be included. If field_filter is None, that all fields will be used. |
||||
self.field_filter = field_filter |
||||
|
||||
def field_dict(self, model): |
||||
""" |
||||
Helper function that returns a dictionary of all fields in the given |
||||
model. If self.field_filter is set, it only includes the fields that |
||||
match the filter. |
||||
""" |
||||
if self.field_filter: |
||||
return dict([(f.name, f) for f in model._meta.fields if self.field_filter(f)]) |
||||
else: |
||||
return dict([(f.name, f) for f in model._meta.fields if not f.rel and not f.primary_key and not f.unique and not isinstance(f, (models.AutoField, models.TextField))]) |
||||
|
||||
def model_index_html(self, request, model, site): |
||||
fields = self.field_dict(model) |
||||
if not fields: |
||||
return u'' |
||||
return mark_safe(u'<p class="filter"><strong>View by:</strong> %s</p>' % \ |
||||
u', '.join(['<a href="fields/%s/">%s</a>' % (f.name, force_unicode(capfirst(f.verbose_name))) for f in fields.values()])) |
||||
|
||||
def urls(self, plugin_name, easy_instance_field): |
||||
if easy_instance_field.field in self.field_dict(easy_instance_field.model.model).values(): |
||||
field_value = smart_str(easy_instance_field.raw_value) |
||||
return [mark_safe(u'%s%s/%s/%s/' % ( |
||||
easy_instance_field.model.url(), |
||||
plugin_name, easy_instance_field.field.name, |
||||
urllib.quote(field_value, safe='')))] |
||||
|
||||
def model_view(self, request, model_databrowse, url): |
||||
self.model, self.site = model_databrowse.model, model_databrowse.site |
||||
self.fields = self.field_dict(self.model) |
||||
|
||||
# If the model has no fields with choices, there's no point in going |
||||
# further. |
||||
if not self.fields: |
||||
raise http.Http404('The requested model has no fields.') |
||||
|
||||
if url is None: |
||||
return self.homepage_view(request) |
||||
url_bits = url.split('/', 1) |
||||
if self.fields.has_key(url_bits[0]): |
||||
return self.field_view(request, self.fields[url_bits[0]], *url_bits[1:]) |
||||
|
||||
raise http.Http404('The requested page does not exist.') |
||||
|
||||
def homepage_view(self, request): |
||||
easy_model = EasyModel(self.site, self.model) |
||||
field_list = self.fields.values() |
||||
field_list.sort(lambda x, y: cmp(x.verbose_name, y.verbose_name)) |
||||
return render_to_response('databrowse/fieldchoice_homepage.html', {'root_url': self.site.root_url, 'model': easy_model, 'field_list': field_list}) |
||||
|
||||
def field_view(self, request, field, value=None): |
||||
easy_model = EasyModel(self.site, self.model) |
||||
easy_field = easy_model.field(field.name) |
||||
if value is not None: |
||||
obj_list = easy_model.objects(**{field.name: value}) |
||||
return render_to_response('databrowse/fieldchoice_detail.html', {'root_url': self.site.root_url, 'model': easy_model, 'field': easy_field, 'value': value, 'object_list': obj_list}) |
||||
obj_list = [v[field.name] for v in self.model._default_manager.distinct().order_by(field.name).values(field.name)] |
||||
return render_to_response('databrowse/fieldchoice_list.html', {'root_url': self.site.root_url, 'model': easy_model, 'field': easy_field, 'object_list': obj_list}) |
||||
@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
|
||||
from django import http |
||||
from django.contrib.databrowse.datastructures import EasyModel |
||||
from django.contrib.databrowse.sites import DatabrowsePlugin |
||||
from django.shortcuts import render_to_response |
||||
import urlparse |
||||
|
||||
class ObjectDetailPlugin(DatabrowsePlugin): |
||||
def model_view(self, request, model_databrowse, url): |
||||
# If the object ID wasn't provided, redirect to the model page, which is one level up. |
||||
if url is None: |
||||
return http.HttpResponseRedirect(urlparse.urljoin(request.path, '../')) |
||||
easy_model = EasyModel(model_databrowse.site, model_databrowse.model) |
||||
obj = easy_model.object_by_pk(url) |
||||
return render_to_response('databrowse/object_detail.html', {'object': obj, 'root_url': model_databrowse.site.root_url}) |
||||
@ -0,0 +1,149 @@
@@ -0,0 +1,149 @@
|
||||
from django import http |
||||
from django.db import models |
||||
from django.contrib.databrowse.datastructures import EasyModel |
||||
from django.shortcuts import render_to_response |
||||
from django.utils.safestring import mark_safe |
||||
|
||||
class AlreadyRegistered(Exception): |
||||
pass |
||||
|
||||
class NotRegistered(Exception): |
||||
pass |
||||
|
||||
class DatabrowsePlugin(object): |
||||
def urls(self, plugin_name, easy_instance_field): |
||||
""" |
||||
Given an EasyInstanceField object, returns a list of URLs for this |
||||
plugin's views of this object. These URLs should be absolute. |
||||
|
||||
Returns None if the EasyInstanceField object doesn't get a |
||||
list of plugin-specific URLs. |
||||
""" |
||||
return None |
||||
|
||||
def model_index_html(self, request, model, site): |
||||
""" |
||||
Returns a snippet of HTML to include on the model index page. |
||||
""" |
||||
return '' |
||||
|
||||
def model_view(self, request, model_databrowse, url): |
||||
""" |
||||
Handles main URL routing for a plugin's model-specific pages. |
||||
""" |
||||
raise NotImplementedError |
||||
|
||||
class ModelDatabrowse(object): |
||||
plugins = {} |
||||
|
||||
def __init__(self, model, site): |
||||
self.model = model |
||||
self.site = site |
||||
|
||||
def root(self, request, url): |
||||
""" |
||||
Handles main URL routing for the databrowse app. |
||||
|
||||
`url` is the remainder of the URL -- e.g. 'objects/3'. |
||||
""" |
||||
# Delegate to the appropriate method, based on the URL. |
||||
if url is None: |
||||
return self.main_view(request) |
||||
try: |
||||
plugin_name, rest_of_url = url.split('/', 1) |
||||
except ValueError: # need more than 1 value to unpack |
||||
plugin_name, rest_of_url = url, None |
||||
try: |
||||
plugin = self.plugins[plugin_name] |
||||
except KeyError: |
||||
raise http.Http404('A plugin with the requested name does not exist.') |
||||
return plugin.model_view(request, self, rest_of_url) |
||||
|
||||
def main_view(self, request): |
||||
easy_model = EasyModel(self.site, self.model) |
||||
html_snippets = mark_safe(u'\n'.join([p.model_index_html(request, self.model, self.site) for p in self.plugins.values()])) |
||||
return render_to_response('databrowse/model_detail.html', { |
||||
'model': easy_model, |
||||
'root_url': self.site.root_url, |
||||
'plugin_html': html_snippets, |
||||
}) |
||||
|
||||
class DatabrowseSite(object): |
||||
def __init__(self): |
||||
self.registry = {} # model_class -> databrowse_class |
||||
self.root_url = None |
||||
|
||||
def register(self, model_or_iterable, databrowse_class=None, **options): |
||||
""" |
||||
Registers the given model(s) with the given databrowse site. |
||||
|
||||
The model(s) should be Model classes, not instances. |
||||
|
||||
If a databrowse class isn't given, it will use DefaultModelDatabrowse |
||||
(the default databrowse options). |
||||
|
||||
If a model is already registered, this will raise AlreadyRegistered. |
||||
""" |
||||
databrowse_class = databrowse_class or DefaultModelDatabrowse |
||||
if issubclass(model_or_iterable, models.Model): |
||||
model_or_iterable = [model_or_iterable] |
||||
for model in model_or_iterable: |
||||
if model in self.registry: |
||||
raise AlreadyRegistered('The model %s is already registered' % model.__name__) |
||||
self.registry[model] = databrowse_class |
||||
|
||||
def unregister(self, model_or_iterable): |
||||
""" |
||||
Unregisters the given model(s). |
||||
|
||||
If a model isn't already registered, this will raise NotRegistered. |
||||
""" |
||||
if issubclass(model_or_iterable, models.Model): |
||||
model_or_iterable = [model_or_iterable] |
||||
for model in model_or_iterable: |
||||
if model not in self.registry: |
||||
raise NotRegistered('The model %s is not registered' % model.__name__) |
||||
del self.registry[model] |
||||
|
||||
def root(self, request, url): |
||||
""" |
||||
Handles main URL routing for the databrowse app. |
||||
|
||||
`url` is the remainder of the URL -- e.g. 'comments/comment/'. |
||||
""" |
||||
self.root_url = request.path[:len(request.path) - len(url)] |
||||
url = url.rstrip('/') # Trim trailing slash, if it exists. |
||||
|
||||
if url == '': |
||||
return self.index(request) |
||||
elif '/' in url: |
||||
return self.model_page(request, *url.split('/', 2)) |
||||
|
||||
raise http.Http404('The requested databrowse page does not exist.') |
||||
|
||||
def index(self, request): |
||||
m_list = [EasyModel(self, m) for m in self.registry.keys()] |
||||
return render_to_response('databrowse/homepage.html', {'model_list': m_list, 'root_url': self.root_url}) |
||||
|
||||
def model_page(self, request, app_label, model_name, rest_of_url=None): |
||||
""" |
||||
Handles the model-specific functionality of the databrowse site, delegating |
||||
to the appropriate ModelDatabrowse class. |
||||
""" |
||||
model = models.get_model(app_label, model_name) |
||||
if model is None: |
||||
raise http.Http404("App %r, model %r, not found." % (app_label, model_name)) |
||||
try: |
||||
databrowse_class = self.registry[model] |
||||
except KeyError: |
||||
raise http.Http404("This model exists but has not been registered with databrowse.") |
||||
return databrowse_class(model, self).root(request, rest_of_url) |
||||
|
||||
site = DatabrowseSite() |
||||
|
||||
from django.contrib.databrowse.plugins.calendars import CalendarPlugin |
||||
from django.contrib.databrowse.plugins.objects import ObjectDetailPlugin |
||||
from django.contrib.databrowse.plugins.fieldchoices import FieldChoicePlugin |
||||
|
||||
class DefaultModelDatabrowse(ModelDatabrowse): |
||||
plugins = {'objects': ObjectDetailPlugin(), 'calendars': CalendarPlugin(), 'fields': FieldChoicePlugin()} |
||||
@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
|
||||
from django.conf.urls.defaults import * |
||||
from django.contrib.databrowse import views |
||||
|
||||
# Note: The views in this URLconf all require a 'models' argument, |
||||
# which is a list of model classes (*not* instances). |
||||
|
||||
urlpatterns = patterns('', |
||||
#(r'^$', views.homepage), |
||||
#(r'^([^/]+)/([^/]+)/$', views.model_detail), |
||||
|
||||
(r'^([^/]+)/([^/]+)/fields/(\w+)/$', views.choice_list), |
||||
(r'^([^/]+)/([^/]+)/fields/(\w+)/(.*)/$', views.choice_detail), |
||||
|
||||
#(r'^([^/]+)/([^/]+)/calendars/(\w+)/$', views.calendar_main), |
||||
#(r'^([^/]+)/([^/]+)/calendars/(\w+)/(\d{4})/$', views.calendar_year), |
||||
#(r'^([^/]+)/([^/]+)/calendars/(\w+)/(\d{4})/(\w{3})/$', views.calendar_month), |
||||
#(r'^([^/]+)/([^/]+)/calendars/(\w+)/(\d{4})/(\w{3})/(\d{1,2})/$', views.calendar_day), |
||||
|
||||
#(r'^([^/]+)/([^/]+)/objects/(.*)/$', views.object_detail), |
||||
) |
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue