Merge github.com:web2py/web2py

This commit is contained in:
Michele Comitini
2013-01-16 12:22:56 +01:00
11 changed files with 814 additions and 225 deletions
+1 -1
View File
@@ -1 +1 @@
Version 2.4.1-alpha.2+timestamp.2013.01.09.16.56.34
Version 2.4.1-alpha.2+timestamp.2013.01.15.22.22.06
+37 -26
View File
@@ -15,7 +15,13 @@ from glob import glob
import shutil
import platform
try:
from git import *
import git
GIT_ERRORS = (git.GitCommandError, git.InvalidGitRepositoryError,
git.NoSuchPathError)
if git.__version__ >= '0.3.1':
GIT_ERRORS += (git.CacheError, git.CheckoutError,
git.ODBError, git.ParseError,
git.UnmergedEntriesError)
have_git = True
except ImportError:
have_git = False
@@ -240,10 +246,10 @@ def site():
redirect(URL(r=request))
target = os.path.join(apath(r=request), form_update.vars.name)
try:
new_repo = Repo.clone_from(form_update.vars.url, target)
new_repo = git.Repo.clone_from(form_update.vars.url, target)
session.flash = T('new application "%s" imported',
form_update.vars.name)
except GitCommandError, err:
except git.GitCommandError, err:
session.flash = T('Invalid git repository specified.')
redirect(URL(r=request))
@@ -1726,29 +1732,31 @@ def git_pull():
{T('Cancel'): URL('site')})
if dialog.accepted:
try:
repo = Repo(os.path.join(apath(r=request), app))
repo = git.Repo(os.path.join(apath(r=request), app))
origin = repo.remotes.origin
origin.fetch()
origin.pull()
session.flash = T("Application updated via git pull")
redirect(URL('site'))
except CheckoutError, message:
session.flash = T("Pull failed, certain files could not be checked out. Check logs for details.")
redirect(URL('site'))
except UnmergedEntriesError:
session.flash = T("Pull is not possible because you have unmerged files. Fix them up in the work tree, and then try again.")
redirect(URL('site'))
except GIT_ERRORS, e:
error_type = type(e)
if 'CheckoutError' in error_type:
session.flash = T("Pull failed, certain files could not be checked out. Check logs for details.")
redirect(URL('site'))
elif 'UnmergedEntriesError' in error_type:
session.flash = T("Pull is not possible because you have unmerged files. Fix them up in the work tree, and then try again.")
redirect(URL('site'))
elif 'GitCommandError' in error_type:
session.flash = T(
"Pull failed, git exited abnormally. See logs for details.")
redirect(URL('site'))
else:
session.flash = T(
"Git error: %s %s" % (error_type, str(e)))
redirect(URL('site'))
except AssertionError:
session.flash = T("Pull is not possible because you have unmerged files. Fix them up in the work tree, and then try again.")
redirect(URL('site'))
except GitCommandError, status:
session.flash = T(
"Pull failed, git exited abnormally. See logs for details.")
redirect(URL('site'))
except Exception, e:
session.flash = T(
"Pull failed, git exited abnormally. See logs for details.")
redirect(URL('site'))
elif 'cancel' in request.vars:
redirect(URL('site'))
return dict(app=app, dialog=dialog)
@@ -1766,7 +1774,7 @@ def git_push():
form.process()
if form.accepted:
try:
repo = Repo(os.path.join(apath(r=request), app))
repo = git.Repo(os.path.join(apath(r=request), app))
index = repo.index
index.add([apath(r=request) + app + '/*'])
new_commit = index.commit(form.vars.changelog)
@@ -1775,11 +1783,14 @@ def git_push():
session.flash = T(
"Git repo updated with latest application changes.")
redirect(URL('site'))
except UnmergedEntriesError:
session.flash = T("Push failed, there are unmerged entries in the cache. Resolve merge issues manually and try again.")
redirect(URL('site'))
except Exception, e:
session.flash = T(
"Push failed, git exited abnormally. See logs for details.")
redirect(URL('site'))
except GIT_ERRORS, e:
error_type = type(e)
if "UnmergedEntriesError" in error_type:
session.flash = T("Push failed, there are unmerged entries in the cache. Resolve merge issues manually and try again.")
redirect(URL('site'))
else:
session.flash = T(
"Git error: %s %s" % (error_type, str(e)))
redirect(URL('site'))
return dict(app=app, form=form)
+2 -2
View File
@@ -92,7 +92,7 @@
'Database': 'База данных',
'database': 'база данных',
'database %s select': 'Выбор базы данных %s ',
'database administration': 'администраторирование базы данных',
'database administration': 'администрирование базы данных',
'Date and Time': 'Дата и время',
'db': 'бд',
'DB Model': 'Модель БД',
@@ -134,7 +134,7 @@
'Editing Language file': 'Правка языкового файла',
'Editing Plural Forms File': 'Editing Plural Forms File',
'Enterprise Web Framework': 'Enterprise Web Framework',
'Error logs for "%(app)s"': 'Журнал ошибок для "%(app)"',
'Error logs for "%(app)s"': 'Журнал ошибок для "%(app)s"',
'Error snapshot': 'Error snapshot',
'Error ticket': 'Error ticket',
'Errors': 'Ошибка',
+212
View File
@@ -0,0 +1,212 @@
# coding: utf8
{
'!langcode!': 'sr-cr',
'!langname!': 'Српски (Ћирилица)',
'%Y-%m-%d': '%d-%m-%Y',
'%Y-%m-%d %H:%M:%S': '%d-%m-%Y %H:%M:%S',
'(requires internet access)': '(захтијева приступ интернету)',
'(something like "it-it")': '(нешто као "it-it")',
'@markmin\x01(file **gluon/contrib/plural_rules/%s.py** is not found)': '(датотека **gluon/contrib/plural_rules/%s.py** није пронађена)',
'About': 'Информације',
'About application': 'О апликацији',
'Additional code for your application': 'Додатни код за апликацију',
'admin disabled because unable to access password file': 'администрација онемогућена јер не могу приступити датотеци са лозинком',
'Admin language': 'Језик администратора',
'administrative interface': 'административни интерфејс',
'Administrator Password:': 'Лозинка администратора:',
'and rename it:': 'и преименуј у:',
'Application name:': 'Назив апликације:',
'are not used': 'није кориштено',
'are not used yet': 'није још кориштено',
'Are you sure you want to delete this object?': 'Да ли сте сигурни да желите обрисати?',
'arguments': 'arguments',
'at char %s': 'код слова %s',
'at line %s': 'на линији %s',
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.',
'back': 'назад',
'Basics': 'Основе',
'Begin': 'Почетак',
'cache, errors and sessions cleaned': 'кеш, грешке и сесије су обрисани',
'can be a git repo': 'може бити git repo',
'cannot upload file "%(filename)s"': 'не мофу отпремити датотеку "%(filename)s"',
'Change admin password': 'Промијени лзинку администратора',
'check all': 'check all',
'Check for upgrades': 'Провјери могућност надоградње',
'Checking for upgrades...': 'Провјеравам могућност надоградње...',
'Clean': 'Прочисти',
'Click row to expand traceback': 'Click row to expand traceback',
'code': 'код',
'collapse/expand all': 'сакрити/приказати све',
'Compile': 'Компајлирај',
'Controllers': 'Контролери',
'controllers': 'контролери',
'Count': 'Count',
'Create': 'Креирај',
'create file with filename:': 'Креирај датотеку под називом:',
'Create rules': 'Креирај правила',
'created by': 'израдио',
'crontab': 'crontab',
'currently running': 'тренутно покренут',
'currently saved or': 'тренутно сачувано или',
'database administration': 'администрација базе података',
'Debug': 'Debug',
'defines tables': 'дефинише табеле',
'delete': 'обриши',
'Delete': 'Обриши',
'delete all checked': 'delete all checked',
'Delete this file (you will be asked to confirm deletion)': 'Обриши ову даатотеку (бићете упитани за потврду брисања)',
'Deploy': 'Постави',
'Deploy on Google App Engine': 'Постави на Google App Engine',
'Deploy to OpenShift': 'Постави на OpenShift',
'Detailed traceback description': 'Detailed traceback description',
'direction: ltr': 'direction: ltr',
'Disable': 'Искључи',
'docs': 'документација',
'download layouts': 'преузми layouts',
'download plugins': 'преузми plugins',
'Edit': 'Уређивање',
'edit all': 'уреди све',
'Edit application': 'Уреди апликацију',
'edit controller': 'уреди контролер',
'edit views:': 'уреди views:',
'Editing file "%s"': 'Уређивање датотеке "%s"',
'Editing Language file': 'Уређивање језичке датотеке',
'Error': 'Грешка',
'Error logs for "%(app)s"': 'Преглед грешака за "%(app)s"',
'Error snapshot': 'Error snapshot',
'Error ticket': 'Error ticket',
'Errors': 'Грешке',
'Exception instance attributes': 'Exception instance attributes',
'Expand Abbreviation': 'Expand Abbreviation',
'exposes': 'exposes',
'exposes:': 'exposes:',
'extends': 'проширује',
'failed to compile file because:': 'нисам могао да компајлирам због:',
'File': 'Датотека',
'file does not exist': 'датотека не постоји',
'file saved on %s': 'датотека сачувана на %s',
'filter': 'филтер',
'Find Next': 'Пронађи сљедећи',
'Find Previous': 'Пронађи претходни',
'Frames': 'Frames',
'Functions with no doctests will result in [passed] tests.': 'Functions with no doctests will result in [passed] tests.',
'Generate': 'Generate',
'Get from URL:': 'Преузми са странице:',
'Git Pull': 'Git Pull',
'Git Push': 'Git Push',
'Go to Matching Pair': 'Go to Matching Pair',
'go!': 'крени!',
'Help': 'Помоћ',
'Hide/Show Translated strings': 'Сакрити/Приказати преведене ријечи',
'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.': 'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.',
'includes': 'укључује',
'inspect attributes': 'inspect attributes',
'Install': 'Инсталирај',
'Installed applications': 'Инсталиране апликације',
'invalid password.': 'погрешна лозинка.',
'Key bindings': 'Пречице',
'Key bindings for ZenCoding Plugin': 'Пречице за ZenCoding Plugin',
'Language files (static strings) updated': 'Језичке датотеке су ажуриране',
'languages': 'језици',
'Languages': 'Језици',
'Last saved on:': 'Посљедња измјена:',
'License for': 'Лиценца за',
'loading...': 'преузимам...',
'locals': 'locals',
'Login': 'Пријава',
'Login to the Administrative Interface': 'Пријава за административни интерфејс',
'Logout': 'Излаз',
'Match Pair': 'Match Pair',
'Merge Lines': 'Споји линије',
'models': 'models',
'Models': 'Models',
'Modules': 'Modules',
'modules': 'modules',
'New Application Wizard': 'Чаробњак за нове апликације',
'New application wizard': 'Чаробњак за нове апликације',
'New simple application': 'Нова једноставна апликација',
'Next Edit Point': 'Next Edit Point',
'No ticket_storage.txt found under /private folder': 'No ticket_storage.txt found under /private folder',
'online designer': 'онлајн дизајнер',
'Original/Translation': 'Оргинал/Превод',
'Overwrite installed app': 'Пребриши постојећу апликацију',
'Pack all': 'Запакуј све',
'Peeking at file': 'Peeking at file',
'Plugins': 'Plugins',
'plugins': 'plugins',
'Plural-Forms:': 'Plural-Forms:',
'Powered by': 'Омогућио',
'Previous Edit Point': 'Previous Edit Point',
'Private files': 'Private files',
'private files': 'private files',
'Project Progress': 'Напредак пројекта',
'Reload routes': 'Обнови преусмјерења',
'Removed Breakpoint on %s at line %s': 'Removed Breakpoint on %s at line %s',
'Replace': 'Замијени',
'Replace All': 'Замијени све',
'request': 'request',
'response': 'response',
'restart': 'restart',
'restore': 'restore',
'revert': 'revert',
'rules are not defined': 'правила нису дефинисана',
'rules:': 'правила:',
"Run tests in this file (to run all files, you may also use the button labelled 'test')": "Run tests in this file (to run all files, you may also use the button labelled 'test')",
'Running on %s': 'Покренути на %s',
'Save': 'Сачувај',
'Save via Ajax': 'сачувај via Ajax',
'Saved file hash:': 'Сачувано као хаш:',
'session': 'сесија',
'session expired': 'сесија истекла',
'Set Breakpoint on %s at line %s: %s': 'Set Breakpoint on %s at line %s: %s',
'shell': 'shell',
'Site': 'Сајт',
'skip to generate': 'skip to generate',
'Start a new app': 'Покрени нову апликацију',
'Start searching': 'Покрени претрагу',
'Start wizard': 'Покрени чаробњака',
'static': 'static',
'Static files': 'Static files',
'Step': 'Корак',
'Submit': 'Прихвати',
'successful': 'успјешан',
'test': 'тест',
'Testing application': 'Тестирање апликације',
'The application logic, each URL path is mapped in one exposed function in the controller': 'The application logic, each URL path is mapped in one exposed function in the controller',
'The data representation, define database tables and sets': 'The data representation, define database tables and sets',
'The presentations layer, views are also known as templates': 'The presentations layer, views are also known as templates',
'There are no models': 'There are no models',
'There are no plugins': 'There are no plugins',
'There are no private files': 'There are no private files',
'These files are not served, they are only available from within your app': 'These files are not served, they are only available from within your app',
'These files are served without processing, your images go here': 'These files are served without processing, your images go here',
'Ticket ID': 'Ticket ID',
'Ticket Missing': 'Ticket nedostaje',
'to previous version.': 'на претходну верзију.',
'To create a plugin, name a file/folder plugin_[name]': 'To create a plugin, name a file/folder plugin_[name]',
'toggle breakpoint': 'toggle breakpoint',
'Toggle Fullscreen': 'Toggle Fullscreen',
'Traceback': 'Traceback',
'Translation strings for the application': 'Ријечи у апликацији које треба превести',
'Try the mobile interface': 'Пробај мобилни интерфејс',
'try view': 'try view',
'uncheck all': 'uncheck all',
'Uninstall': 'Деинсталирај',
'update': 'ажурирај',
'update all languages': 'ажурирај све језике',
'upload': 'Отпреми',
'Upload a package:': 'Преузми пакет:',
'Upload and install packed application': 'Преузми и инсталирај запаковану апликацију',
'upload file:': 'преузми датотеку:',
'upload plugin file:': 'преузми плагин датотеку:',
'variables': 'variables',
'Version': 'Верзија',
'Version %s.%s.%s (%s) %s': 'Верзија %s.%s.%s (%s) %s',
'Versioning': 'Versioning',
'views': 'views',
'Views': 'Views',
'Web Framework': 'Web Framework',
'web2py is up to date': 'web2py је ажуран',
'web2py Recent Tweets': 'web2py Recent Tweets',
'Wrap with Abbreviation': 'Wrap with Abbreviation',
}
+212
View File
@@ -0,0 +1,212 @@
# coding: utf8
{
'!langcode!': 'sr-lt',
'!langname!': 'Srpski (Latinica)',
'%Y-%m-%d': '%d-%m-%Y',
'%Y-%m-%d %H:%M:%S': '%d-%m-%Y %H:%M:%S',
'(requires internet access)': '(zahtijeva pristup internetu)',
'(something like "it-it")': '(nešto kao "it-it")',
'@markmin\x01(file **gluon/contrib/plural_rules/%s.py** is not found)': '(datoteka **gluon/contrib/plural_rules/%s.py** nije pronađena)',
'About': 'Informacije',
'About application': 'O aplikaciji',
'Additional code for your application': 'Dodatni kod za aplikaciju',
'admin disabled because unable to access password file': 'administracija onemogućena jer ne mogu pristupiti datoteci sa lozinkom',
'Admin language': 'Jezik administratora',
'administrative interface': 'administrativni interfejs',
'Administrator Password:': 'Lozinka administratora:',
'and rename it:': 'i preimenuj u:',
'Application name:': 'Naziv aplikacije:',
'are not used': 'nije korišteno',
'are not used yet': 'nije još korišteno',
'Are you sure you want to delete this object?': 'Da li ste sigurni da želite obrisati?',
'arguments': 'arguments',
'at char %s': 'kod slova %s',
'at line %s': 'na liniji %s',
'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.': 'ATTENTION: TESTING IS NOT THREAD SAFE SO DO NOT PERFORM MULTIPLE TESTS CONCURRENTLY.',
'back': 'nazad',
'Basics': 'Osnove',
'Begin': 'Početak',
'cache, errors and sessions cleaned': 'keš, greške i sesije su obrisani',
'can be a git repo': 'može biti git repo',
'cannot upload file "%(filename)s"': 'ne mogu otpremiti datoteku "%(filename)s"',
'Change admin password': 'Promijeni lozinku administratora',
'check all': 'check all',
'Check for upgrades': 'Provjeri mogućnost nadogradnje',
'Checking for upgrades...': 'Provjeravam mogućnost nadogradnje...',
'Clean': 'Pročisti',
'Click row to expand traceback': 'Click row to expand traceback',
'code': 'kod',
'collapse/expand all': 'sakriti/prikazati sve',
'Compile': 'Kompajliraj',
'Controllers': 'Kontroleri',
'controllers': 'kontroleri',
'Count': 'Count',
'Create': 'Kreiraj',
'create file with filename:': 'Kreiraj datoteku pod nazivom:',
'Create rules': 'Kreiraj pravila',
'created by': 'izradio',
'crontab': 'crontab',
'currently running': 'trenutno pokrenut',
'currently saved or': 'trenutno sačuvano ili',
'database administration': 'administracija baze podataka',
'Debug': 'Debug',
'defines tables': 'definiše tabele',
'delete': 'obriši',
'Delete': 'Obriši',
'delete all checked': 'delete all checked',
'Delete this file (you will be asked to confirm deletion)': 'Obriši ovu datoteku (bićete upitani za potvrdu brisanja)',
'Deploy': 'Postavi',
'Deploy on Google App Engine': 'Postavi na Google App Engine',
'Deploy to OpenShift': 'Postavi na OpenShift',
'Detailed traceback description': 'Detailed traceback description',
'direction: ltr': 'direction: ltr',
'Disable': 'Isključi',
'docs': 'dokumentacija',
'download layouts': 'preuzmi layouts',
'download plugins': 'preuzmi plugins',
'Edit': 'Uređivanje',
'edit all': 'uredi sve',
'Edit application': 'Uredi aplikaciju',
'edit controller': 'uredi controller',
'edit views:': 'uredi views:',
'Editing file "%s"': 'Uređivanje datoteke "%s"',
'Editing Language file': 'Uređivanje jezičke datoteke',
'Error': 'Greška',
'Error logs for "%(app)s"': 'Pregled grešaka za "%(app)s"',
'Error snapshot': 'Error snapshot',
'Error ticket': 'Error ticket',
'Errors': 'Greške',
'Exception instance attributes': 'Exception instance attributes',
'Expand Abbreviation': 'Expand Abbreviation',
'exposes': 'exposes',
'exposes:': 'exposes:',
'extends': 'proširuje',
'failed to compile file because:': 'nisam mogao da kompajliram zbog:',
'File': 'Datoteka',
'file does not exist': 'datoteka ne postoji',
'file saved on %s': 'datoteka sačuvana na %s',
'filter': 'filter',
'Find Next': 'Pronađi sljedeći',
'Find Previous': 'Pronađi prethodni',
'Frames': 'Frames',
'Functions with no doctests will result in [passed] tests.': 'Functions with no doctests will result in [passed] tests.',
'Generate': 'Generate',
'Get from URL:': 'Preuzmi sa stranice:',
'Git Pull': 'Git Pull',
'Git Push': 'Git Push',
'Go to Matching Pair': 'Go to Matching Pair',
'go!': 'kreni!',
'Help': 'Pomoć',
'Hide/Show Translated strings': 'Sakriti/Prikazati prevedene riječi',
'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.': 'If the report above contains a ticket number it indicates a failure in executing the controller, before any attempt to execute the doctests. This is usually due to an indentation error or an error outside function code.\nA green title indicates that all tests (if defined) passed. In this case test results are not shown.',
'includes': 'uključuje',
'inspect attributes': 'inspect attributes',
'Install': 'Instaliraj',
'Installed applications': 'Instalirane aplikacije',
'invalid password.': 'pogrešna lozinka.',
'Key bindings': 'Prečice',
'Key bindings for ZenCoding Plugin': 'Prečice za for ZenCoding Plugin',
'Language files (static strings) updated': 'Jezičke datoteke su ažurirane',
'languages': 'jezici',
'Languages': 'Jezici',
'Last saved on:': 'Posljednja izmjena:',
'License for': 'Licenca za',
'loading...': 'preuzimam...',
'locals': 'locals',
'Login': 'Prijava',
'Login to the Administrative Interface': 'Prijava za administrativni interfejs',
'Logout': 'Izlaz',
'Match Pair': 'Match Pair',
'Merge Lines': 'Spoji linije',
'models': 'models',
'Models': 'Models',
'Modules': 'Modules',
'modules': 'modules',
'New Application Wizard': 'Čarobnjak za nove aplikacije',
'New application wizard': 'Čarobnjak za nove aplikacije',
'New simple application': 'Nova jednostavna aplikacija',
'Next Edit Point': 'Next Edit Point',
'No ticket_storage.txt found under /private folder': 'No ticket_storage.txt found under /private folder',
'online designer': 'onlajn dizajner',
'Original/Translation': 'Original/Prevod',
'Overwrite installed app': 'Prebriši postojeću aplikaciju',
'Pack all': 'Zapakuj sve',
'Peeking at file': 'Peeking at file',
'Plugins': 'Plugins',
'plugins': 'plugins',
'Plural-Forms:': 'Plural-Forms:',
'Powered by': 'Omogućio',
'Previous Edit Point': 'Previous Edit Point',
'Private files': 'Private files',
'private files': 'private files',
'Project Progress': 'Napredak projekta',
'Reload routes': 'Obnovi preusmjerenja',
'Removed Breakpoint on %s at line %s': 'Removed Breakpoint on %s at line %s',
'Replace': 'Zamijeni',
'Replace All': 'Zamijeni sve',
'request': 'request',
'response': 'response',
'restart': 'restart',
'restore': 'restore',
'revert': 'revert',
'rules are not defined': 'pravila nisu definisana',
'rules:': 'pravila:',
"Run tests in this file (to run all files, you may also use the button labelled 'test')": "Run tests in this file (to run all files, you may also use the button labelled 'test')",
'Running on %s': 'Pokrenuto na %s',
'Save': 'Sačuvaj',
'Save via Ajax': 'Sačuvaj via Ajax',
'Saved file hash:': 'Sačuvano kao haš:',
'session': 'sesija',
'session expired': 'sesija istekla',
'Set Breakpoint on %s at line %s: %s': 'Set Breakpoint on %s at line %s: %s',
'shell': 'shell',
'Site': 'Sajt',
'skip to generate': 'skip to generate',
'Start a new app': 'Pokreni novu aplikaciju',
'Start searching': 'Pokreni pretragu',
'Start wizard': 'Pokreni čarobnjaka',
'static': 'static',
'Static files': 'Static files',
'Step': 'Korak',
'Submit': 'Prihvati',
'successful': 'uspješan',
'test': 'test',
'Testing application': 'Testing application',
'The application logic, each URL path is mapped in one exposed function in the controller': 'The application logic, each URL path is mapped in one exposed function in the controller',
'The data representation, define database tables and sets': 'The data representation, define database tables and sets',
'The presentations layer, views are also known as templates': 'The presentations layer, views are also known as templates',
'There are no models': 'There are no models',
'There are no plugins': 'There are no plugins',
'There are no private files': 'There are no private files',
'These files are not served, they are only available from within your app': 'These files are not served, they are only available from within your app',
'These files are served without processing, your images go here': 'These files are served without processing, your images go here',
'Ticket ID': 'Ticket ID',
'Ticket Missing': 'Ticket nedostaje',
'to previous version.': 'na prethodnu verziju.',
'To create a plugin, name a file/folder plugin_[name]': 'To create a plugin, name a file/folder plugin_[name]',
'toggle breakpoint': 'toggle breakpoint',
'Toggle Fullscreen': 'Toggle Fullscreen',
'Traceback': 'Traceback',
'Translation strings for the application': 'Riječi u aplikaciji koje treba prevesti',
'Try the mobile interface': 'Probaj mobilni interfejs',
'try view': 'try view',
'uncheck all': 'uncheck all',
'Uninstall': 'Deinstaliraj',
'update': 'ažuriraj',
'update all languages': 'ažuriraj sve jezike',
'upload': 'Otpremi',
'Upload a package:': 'Preuzmi paket:',
'Upload and install packed application': 'Preuzmi i instaliraj zapakovanu aplikaciju',
'upload file:': 'preuzmi datoteku:',
'upload plugin file:': 'preuzmi plugin datoteku:',
'variables': 'variables',
'Version': 'Verzija',
'Version %s.%s.%s (%s) %s': 'Verzija %s.%s.%s (%s) %s',
'Versioning': 'Versioning',
'views': 'views',
'Views': 'Views',
'Web Framework': 'Web Framework',
'web2py is up to date': 'web2py je ažuran',
'web2py Recent Tweets': 'web2py Recent Tweets',
'Wrap with Abbreviation': 'Wrap with Abbreviation',
}
+264 -137
View File
@@ -447,7 +447,7 @@ def pluralize(singular, rules=PLURALIZE_RULES):
if plural: return plural
def hide_password(uri):
return REGEX_PASSWORD.sub('://******:',uri)
return REGEX_NOPASSWD.sub('******',uri)
def OR(a,b):
return a|b
@@ -5565,7 +5565,8 @@ class IMAPAdapter(NoSQLAdapter):
subject string
mime string The mime header declaration
email string The complete RFC822 message**
attachments list:string Each non text decoded part as string
attachments <type list> Each non text part as dict
encoding string The main detected encoding
*At the application side it is measured as the length of the RFC822
message string
@@ -5623,11 +5624,22 @@ class IMAPAdapter(NoSQLAdapter):
# mapped names (which native
# mailbox has what table name)
db.mailboxes <dict> # tablename, server native name pairs
imapdb.mailboxes <dict> # tablename, server native name pairs
# To retrieve a table native mailbox name use:
db.<table>.mailbox
imapdb.<table>.mailbox
### New features v2.4.1:
# Declare mailboxes statically with tablename, name pairs
# This avoids the extra server names retrieval
imapdb.define_tables({"inbox": "INBOX"})
# Selects without content/attachments/email columns will only
# fetch header and flags
imapdb(q).select(imapdb.INBOX.sender, imapdb.INBOX.subject)
"""
types = {
@@ -5670,6 +5682,7 @@ class IMAPAdapter(NoSQLAdapter):
self.driver_args = driver_args
self.adapter_args = adapter_args
self.mailbox_size = None
self.static_names = None
self.charset = sys.getfilesystemencoding()
# imap class
self.imap4 = None
@@ -5829,16 +5842,22 @@ class IMAPAdapter(NoSQLAdapter):
else:
return None
@staticmethod
def header_represent(f, r):
from email.header import decode_header
text, encoding = decode_header(f)[0]
return text
def encode_text(self, text, charset, errors="replace"):
""" convert text for mail to unicode"""
if text is None:
text = ""
else:
if isinstance(text, str):
if charset is not None:
text = unicode(text, charset, errors)
else:
if charset is None:
text = unicode(text, "utf-8", errors)
else:
text = unicode(text, charset, errors)
else:
raise Exception("Unsupported mail text type %s" % type(text))
return text.encode("utf-8")
@@ -5847,12 +5866,13 @@ class IMAPAdapter(NoSQLAdapter):
charset = message.get_content_charset()
return charset
def reset_mailboxes(self):
self.connection.mailbox_names = None
self.get_mailboxes()
def get_mailboxes(self):
""" Query the mail database for mailbox names """
if self.static_names:
# statically defined mailbox names
self.connection.mailbox_names = self.static_names
return self.static_names.keys()
mailboxes_list = self.connection.list()
self.connection.mailbox_names = dict()
mailboxes = list()
@@ -5864,7 +5884,8 @@ class IMAPAdapter(NoSQLAdapter):
sub_items = item.split("\"")
sub_items = [sub_item for sub_item in sub_items \
if len(sub_item.strip()) > 0]
mailbox = sub_items[len(sub_items) - 1]
# mailbox = sub_items[len(sub_items) -1]
mailbox = sub_items[-1]
# remove unwanted characters and store original names
# Don't allow leading non alphabetic characters
mailbox_name = re.sub('^[_0-9]*', '', re.sub('[^_\w]','',re.sub('[/ ]','_',mailbox)))
@@ -5896,7 +5917,7 @@ class IMAPAdapter(NoSQLAdapter):
else:
return False
def define_tables(self):
def define_tables(self, mailbox_names=None):
"""
Auto create common IMAP fileds
@@ -5908,11 +5929,18 @@ class IMAPAdapter(NoSQLAdapter):
Returns a dictionary with tablename, server native mailbox name
pairs.
"""
if mailbox_names:
# optional statically declared mailboxes
self.static_names = mailbox_names
else:
self.static_names = None
if not isinstance(self.connection.mailbox_names, dict):
self.get_mailboxes()
mailboxes = self.connection.mailbox_names.keys()
for mailbox_name in mailboxes:
self.db.define_table("%s" % mailbox_name,
names = self.connection.mailbox_names.keys()
for name in names:
self.db.define_table("%s" % name,
Field("uid", "string", writable=False),
Field("answered", "boolean"),
Field("created", "datetime", writable=False),
@@ -5930,13 +5958,19 @@ class IMAPAdapter(NoSQLAdapter):
Field("subject", "string", writable=False),
Field("mime", "string", writable=False),
Field("email", "string", writable=False, readable=False),
Field("attachments", "list:string", writable=False, readable=False),
Field("attachments", list, writable=False, readable=False),
Field("encoding")
)
# Set a special _mailbox attribute for storing
# native mailbox names
self.db[mailbox_name].mailbox = \
self.connection.mailbox_names[mailbox_name]
self.db[name].mailbox = \
self.connection.mailbox_names[name]
# decode quoted printable
self.db[name].to.represent = self.db[name].cc.represent = \
self.db[name].bcc.represent = self.db[name].sender.represent = \
self.db[name].subject.represent = self.header_represent
# Set the db instance mailbox collections
self.db.mailboxes = self.connection.mailbox_names
@@ -5952,7 +5986,7 @@ class IMAPAdapter(NoSQLAdapter):
query = self.common_filter(query, [self.get_query_mailbox(query),])
return str(query)
def select(self,query,fields,attributes):
def select(self, query, fields, attributes):
""" Search and Fetch records and return web2py rows
"""
# move this statement elsewhere (upper-level)
@@ -5960,19 +5994,22 @@ class IMAPAdapter(NoSQLAdapter):
query = self.common_filter(query, [self.get_query_mailbox(query),])
import email
import email.header
decode_header = email.header.decode_header
# get records from imap server with search + fetch
# convert results to a dictionary
tablename = None
fetch_results = list()
if isinstance(query, Query):
tablename = self.get_table(query)
mailbox = self.connection.mailbox_names.get(tablename, None)
if mailbox is not None:
if mailbox is None:
raise ValueError("Mailbox name not found: %s" % mailbox)
else:
# select with readonly
selected = self.connection.select(mailbox, True)
self.mailbox_size = int(selected[1][0])
result, selected = self.connection.select(mailbox, True)
if result != "OK":
raise Exception("IMAP error: %s" % selected)
self.mailbox_size = int(selected[0])
search_query = "(%s)" % str(query).strip()
search_result = self.connection.uid("search", None, search_query)
# Normal IMAP response OK is assumed (change this)
@@ -5981,18 +6018,22 @@ class IMAPAdapter(NoSQLAdapter):
# ten records (change for non-experimental implementation)
# However, light responses are not guaranteed with this
# approach, just fewer messages.
# TODO: change limitby single to 2-tuple argument
limitby = attributes.get('limitby', None)
messages_set = search_result[1][0].split()
# descending order
messages_set.reverse()
if limitby is not None:
# TODO: asc/desc attributes
# TODO: orderby, asc/desc, limitby from complete message set
messages_set = messages_set[int(limitby[0]):int(limitby[1])]
# Partial fetches are not used since the email
# library does not seem to support it (it converts
# partial messages to mangled message instances)
imap_fields = "(RFC822)"
# keep the requests small for header/flags
if any([(field.name in ["content",
"attachments", "email"]) for
field in fields]):
imap_fields = "(RFC822 FLAGS)"
else:
imap_fields = "(RFC822.HEADER FLAGS)"
if len(messages_set) > 0:
# create fetch results object list
# fetch each remote message and store it in memmory
@@ -6008,16 +6049,13 @@ class IMAPAdapter(NoSQLAdapter):
"raw_message": data[0][1]}
fr["multipart"] = fr["email"].is_multipart()
# fetch flags for the message
ftyp, fdata = self.connection.uid("fetch", uid, "(FLAGS)")
if ftyp == "OK":
fr["flags"] = self.driver.ParseFlags(fdata[0])
fetch_results.append(fr)
else:
# error retrieving the flags for this message
pass
fr["flags"] = self.driver.ParseFlags(data[1])
fetch_results.append(fr)
else:
# error retrieving the message body
pass
raise Exception("IMAP error retrieving the body: %s" % data)
else:
raise Exception("IMAP search error: %s" % search_result[1])
elif isinstance(query, (Expression, basestring)):
raise NotImplementedError()
else:
@@ -6033,11 +6071,11 @@ class IMAPAdapter(NoSQLAdapter):
else:
allfields = False
if allfields:
fieldnames = ["%s.%s" % (tablename, field) for field in self.search_fields.keys()]
colnames = ["%s.%s" % (tablename, field) for field in self.search_fields.keys()]
else:
fieldnames = ["%s.%s" % (tablename, field.name) for field in fields]
colnames = ["%s.%s" % (tablename, field.name) for field in fields]
for k in fieldnames:
for k in colnames:
imapfields_dict[k] = k
imapqry_list = list()
@@ -6061,62 +6099,58 @@ class IMAPAdapter(NoSQLAdapter):
# pending: search flags states trough the email message
# instances for correct output
if "%s.id" % tablename in fieldnames:
# preserve subject encoding (ASCII/quoted printable)
if "%s.id" % tablename in colnames:
item_dict["%s.id" % tablename] = n
if "%s.created" % tablename in fieldnames:
if "%s.created" % tablename in colnames:
item_dict["%s.created" % tablename] = self.convert_date(message["Date"])
if "%s.uid" % tablename in fieldnames:
if "%s.uid" % tablename in colnames:
item_dict["%s.uid" % tablename] = uid
if "%s.sender" % tablename in fieldnames:
if "%s.sender" % tablename in colnames:
# If there is no encoding found in the message header
# force utf-8 replacing characters (change this to
# module's defaults). Applies to .sender, .to, .cc and .bcc fields
#############################################################################
# TODO: External function to manage encoding and decoding of message strings
#############################################################################
item_dict["%s.sender" % tablename] = self.encode_text(message["From"], charset)
if "%s.to" % tablename in fieldnames:
item_dict["%s.to" % tablename] = self.encode_text(message["To"], charset)
if "%s.cc" % tablename in fieldnames:
item_dict["%s.sender" % tablename] = message["From"]
if "%s.to" % tablename in colnames:
item_dict["%s.to" % tablename] = message["To"]
if "%s.cc" % tablename in colnames:
if "Cc" in message.keys():
item_dict["%s.cc" % tablename] = self.encode_text(message["Cc"], charset)
item_dict["%s.cc" % tablename] = message["Cc"]
else:
item_dict["%s.cc" % tablename] = ""
if "%s.bcc" % tablename in fieldnames:
if "%s.bcc" % tablename in colnames:
if "Bcc" in message.keys():
item_dict["%s.bcc" % tablename] = self.encode_text(message["Bcc"], charset)
item_dict["%s.bcc" % tablename] = message["Bcc"]
else:
item_dict["%s.bcc" % tablename] = ""
if "%s.deleted" % tablename in fieldnames:
if "%s.deleted" % tablename in colnames:
item_dict["%s.deleted" % tablename] = "\\Deleted" in flags
if "%s.draft" % tablename in fieldnames:
if "%s.draft" % tablename in colnames:
item_dict["%s.draft" % tablename] = "\\Draft" in flags
if "%s.flagged" % tablename in fieldnames:
if "%s.flagged" % tablename in colnames:
item_dict["%s.flagged" % tablename] = "\\Flagged" in flags
if "%s.recent" % tablename in fieldnames:
if "%s.recent" % tablename in colnames:
item_dict["%s.recent" % tablename] = "\\Recent" in flags
if "%s.seen" % tablename in fieldnames:
if "%s.seen" % tablename in colnames:
item_dict["%s.seen" % tablename] = "\\Seen" in flags
if "%s.subject" % tablename in fieldnames:
subject = message["Subject"]
decoded_subject = decode_header(subject)
text = decoded_subject[0][0]
encoding = decoded_subject[0][1]
if encoding in (None, ""):
encoding = charset
item_dict["%s.subject" % tablename] = self.encode_text(text, encoding)
if "%s.answered" % tablename in fieldnames:
if "%s.subject" % tablename in colnames:
item_dict["%s.subject" % tablename] = message["Subject"]
if "%s.answered" % tablename in colnames:
item_dict["%s.answered" % tablename] = "\\Answered" in flags
if "%s.mime" % tablename in fieldnames:
if "%s.mime" % tablename in colnames:
item_dict["%s.mime" % tablename] = message.get_content_type()
if "%s.encoding" % tablename in colnames:
item_dict["%s.encoding" % tablename] = charset
# Here goes the whole RFC822 body as an email instance
# for controller side custom processing
# The message is stored as a raw string
# >> email.message_from_string(raw string)
# returns a Message object for enhanced object processing
if "%s.email" % tablename in fieldnames:
item_dict["%s.email" % tablename] = self.encode_text(raw_message, charset)
if "%s.email" % tablename in colnames:
# WARNING: no encoding performed (raw message)
item_dict["%s.email" % tablename] = raw_message
# Size measure as suggested in a Velocity Reviews post
# by Tim Williams: "how to get size of email attachment"
@@ -6124,18 +6158,31 @@ class IMAPAdapter(NoSQLAdapter):
# To retrieve the server size for representation would add a new
# fetch transaction to the process
for part in message.walk():
if "%s.attachments" % tablename in fieldnames:
if not "text" in part.get_content_maintype():
attachments.append(part.get_payload(decode=True))
if "%s.content" % tablename in fieldnames:
if "text" in part.get_content_maintype():
payload = self.encode_text(part.get_payload(decode=True), charset)
content.append(payload)
if "%s.size" % tablename in fieldnames:
maintype = part.get_content_maintype()
if ("%s.attachments" % tablename in colnames) or \
("%s.content" % tablename in colnames):
if "%s.attachments" % tablename in colnames:
if not ("text" in maintype):
payload = part.get_payload(decode=True)
if payload:
attachment = {
"payload": payload,
"filename": part.get_filename(),
"encoding": part.get_content_charset(),
"mime": part.get_content_type(),
"disposition": part["Content-Disposition"]}
attachments.append(attachment)
if "%s.content" % tablename in colnames:
payload = part.get_payload(decode=True)
part_charset = self.get_charset(part)
if "text" in maintype:
if payload:
content.append(self.encode_text(payload, part_charset))
if "%s.size" % tablename in colnames:
if part is not None:
size += len(str(part))
item_dict["%s.content" % tablename] = bar_encode(content)
item_dict["%s.attachments" % tablename] = bar_encode(attachments)
item_dict["%s.attachments" % tablename] = attachments
item_dict["%s.size" % tablename] = size
imapqry_list.append(item_dict)
@@ -6143,12 +6190,12 @@ class IMAPAdapter(NoSQLAdapter):
# creation (sends an array or lists)
for item_dict in imapqry_list:
imapqry_array_item = list()
for fieldname in fieldnames:
for fieldname in colnames:
imapqry_array_item.append(item_dict[fieldname])
imapqry_array.append(imapqry_array_item)
# parse result and return a rows object
colnames = fieldnames
colnames = colnames
processor = attributes.get('processor',self.parse)
return processor(imapqry_array, fields, colnames)
@@ -6193,6 +6240,8 @@ class IMAPAdapter(NoSQLAdapter):
result, data = self.connection.store(*command)
if result == "OK":
rowcount += 1
else:
raise Exception("IMAP storing error: %s" % data)
return rowcount
def _count(self, query, distinct=None):
@@ -6224,6 +6273,8 @@ class IMAPAdapter(NoSQLAdapter):
result, data = self.connection.store(number, "+FLAGS", "(\\Deleted)")
if result == "OK":
counter += 1
else:
raise Exception("IMAP store error: %s" % data)
if counter > 0:
result, data = self.connection.expunge()
return counter
@@ -6662,6 +6713,89 @@ class Row(object):
del d[k]
return d
def as_xml(self, row_name="row", colnames=None, indent=' '):
def f(row,field,indent=' '):
if isinstance(row,Row):
spc = indent+' \n'
items = [f(row[x],x,indent+' ') for x in row]
return '%s<%s>\n%s\n%s</%s>' % (
indent,
field,
spc.join(item for item in items if item),
indent,
field)
elif not callable(row):
if REGEX_ALPHANUMERIC.match(field):
return '%s<%s>%s</%s>' % (indent,field,row,field)
else:
return '%s<extra name="%s">%s</extra>' % \
(indent,field,row)
else:
return None
return f(self, row_name, indent=indent)
def as_json(self, mode="object", default=None, colnames=None,
serialize=True, **kwargs):
"""
serializes the table to a JSON list of objects
kwargs are passed to .as_dict method
only "object" mode supported for single row
serialize = False used by Rows.as_json
TODO: return array mode with query column order
"""
def inner_loop(record, col):
(t, f) = col.split('.')
res = None
if not REGEX_TABLE_DOT_FIELD.match(col):
key = col
res = record._extra[col]
else:
key = f
if isinstance(record.get(t, None), Row):
res = record[t][f]
else:
res = record[f]
if mode == 'object':
return (key, res)
else:
return res
multi = any([isinstance(v, self.__class__) for v in self.values()])
mode = mode.lower()
if not mode in ['object', 'array']:
raise SyntaxError('Invalid JSON serialization mode: %s' % mode)
if mode=='object' and colnames:
item = dict([inner_loop(self, col) for col in colnames])
elif colnames:
item = [inner_loop(self, col) for col in colnames]
else:
if not mode == 'object':
raise SyntaxError('Invalid JSON serialization mode: %s' % mode)
if multi:
item = dict()
[item.update(**v.as_dict(**kwargs)) for v in self.values()]
else:
item = self.as_dict(**kwargs)
if serialize:
if have_serializers:
return serializers.json(item,
default=default or
serializers.custom_json)
else:
try:
import json as simplejson
except ImportError:
import gluon.contrib.simplejson as simplejson
return simplejson.dumps(item)
else:
return item
################################################################################
# Everything below should be independent of the specifics of the database
# and should work for RDBMs and some NoSQL databases
@@ -6857,7 +6991,7 @@ class DAL(object):
for db in db_group:
if not db._uri:
continue
k = REGEX_NOPASSWD.sub('******',db._uri)
k = hide_password(db._uri)
infos[k] = dict(dbstats = [(row[0], row[1]) for row in db._timings],
dbtables = {'defined':
sorted(list(set(db.tables) -
@@ -7163,6 +7297,8 @@ def index():
i = 0
while i<len(patterns):
pattern = patterns[i]
if not isinstance(pattern,str):
pattern = pattern[0]
tokens = pattern.split('/')
if tokens[-1].startswith(':auto') and re2.match(tokens[-1]):
new_patterns = auto_table(tokens[-1][tokens[-1].find('[')+1:-1],
@@ -7239,15 +7375,21 @@ def index():
ref = tag[tag.find('[')+1:-1]
if '.' in ref and otable:
table,field = ref.split('.')
# print table,field
selfld = '_id'
if db[table][field].type.startswith('reference '):
refs = [ x.name for x in db[otable] if x.type == db[table][field].type ]
else:
refs = [ x.name for x in db[table]._referenced_by if x.tablename==otable ]
if refs:
selfld = refs[0]
if nested_select:
try:
dbset=db(db[table][field].belongs(dbset._select(db[otable]._id)))
dbset=db(db[table][field].belongs(dbset._select(db[otable][selfld])))
except ValueError:
return Row({'status':400,'pattern':pattern,
'error':'invalid path','response':None})
else:
items = [item.id for item in dbset.select(db[otable]._id)]
items = [item.id for item in dbset.select(db[otable][selfld])]
dbset=db(db[table][field].belongs(items))
else:
table = ref
@@ -8062,7 +8204,7 @@ class Table(object):
return fields
def _insert(self, **fields):
fields = self._default(fields)
fields = self._defaults(fields)
return self._db._adapter._insert(self, self._listify(fields))
def insert(self, **fields):
@@ -9461,6 +9603,22 @@ class Rows(object):
:param storage_to_dict: when True returns a dict, otherwise a list(default True)
:param datetime_to_str: convert datetime fields as strings (default True)
"""
# test for multiple rows
multi = False
f = self.first()
if f:
multi = any([isinstance(v, f.__class__) for v in f.values()])
if (not "." in key) and multi:
# No key provided, default to int indices
def new_key():
i = 0
while True:
yield i
i += 1
key_generator = new_key()
key = lambda r: key_generator.next()
rows = self.as_list(compact, storage_to_dict, datetime_to_str, custom_types)
if isinstance(key,str) and key.count('.')==1:
(table, field) = key.split('.')
@@ -9537,64 +9695,30 @@ class Rows(object):
"""
serializes the table using sqlhtml.SQLTABLE (if present)
"""
if strict:
ncols = len(self.colnames)
def f(row,field,indent=' '):
if isinstance(row,Row):
spc = indent+' \n'
items = [f(row[x],x,indent+' ') for x in row]
return '%s<%s>\n%s\n%s</%s>' % (
indent,
field,
spc.join(item for item in items if item),
indent,
field)
elif not callable(row):
if REGEX_ALPHANUMERIC.match(field):
return '%s<%s>%s</%s>' % (indent,field,row,field)
else:
return '%s<extra name="%s">%s</extra>' % \
(indent,field,row)
else:
return None
return '<%s>\n%s\n</%s>' % (
rows_name,
'\n'.join(f(row,row_name) for row in self),
rows_name)
return '<%s>\n%s\n</%s>' % (rows_name,
'\n'.join(row.as_xml(row_name=row_name,
colnames=self.colnames) for
row in self), rows_name)
import sqlhtml
return sqlhtml.SQLTABLE(self).xml()
def json(self, mode='object', default=None):
def as_xml(self,row_name='row',rows_name='rows'):
return self.xml(strict=True, row_name=row_name, rows_name=rows_name)
def as_json(self, mode='object', default=None):
"""
serializes the table to a JSON list of objects
"""
mode = mode.lower()
if not mode in ['object', 'array']:
raise SyntaxError('Invalid JSON serialization mode: %s' % mode)
def inner_loop(record, col):
(t, f) = col.split('.')
res = None
if not REGEX_TABLE_DOT_FIELD.match(col):
key = col
res = record._extra[col]
else:
key = f
if isinstance(record.get(t, None), Row):
res = record[t][f]
else:
res = record[f]
if mode == 'object':
return (key, res)
else:
return res
items = [record.as_json(mode=mode, default=default,
serialize=False,
colnames=self.colnames) for
record in self]
if mode == 'object':
items = [dict([inner_loop(record, col) for col in
self.colnames]) for record in self]
else:
items = [[inner_loop(record, col) for col in self.colnames]
for record in self]
if have_serializers:
return serializers.json(items,
default=default or
@@ -9606,6 +9730,9 @@ class Rows(object):
import gluon.contrib.simplejson as simplejson
return simplejson.dumps(items)
# for consistent naming yet backwards compatible
as_csv = __str__
json = as_json
################################################################################
# dummy function used to define some doctests
+4 -2
View File
@@ -375,7 +375,7 @@ class Response(Storage):
wrapped = streamer(stream, chunk_size=chunk_size)
return wrapped
def download(self, request, db, chunk_size=DEFAULT_CHUNK_SIZE, attachment=True):
def download(self, request, db, chunk_size=DEFAULT_CHUNK_SIZE, attachment=True, download_filename=None):
"""
example of usage in controller::
@@ -403,9 +403,11 @@ class Response(Storage):
raise HTTP(404)
headers = self.headers
headers['Content-Type'] = contenttype(name)
if download_filename == None:
download_filename = filename
if attachment:
headers['Content-Disposition'] = \
'attachment; filename="%s"' % filename.replace('"','\"')
'attachment; filename="%s"' % download_filename.replace('"','\"')
return self.stream(stream, chunk_size=chunk_size, request=request)
def json(self, data, default=None):
+3 -2
View File
@@ -556,7 +556,7 @@ class Scheduler(MetaScheduler):
self.die()
def wrapped_assign_tasks(self, db):
db.commit() # ?don't know if it's useful, let's be completely sure
db.commit() #db.commit() only for Mysql
x = 0
while x < 10:
try:
@@ -571,6 +571,7 @@ class Scheduler(MetaScheduler):
def wrapped_pop_task(self):
db = self.db
db.commit() #another nifty db.commit() only for Mysql
x = 0
while x < 10:
try:
@@ -579,7 +580,7 @@ class Scheduler(MetaScheduler):
break
except:
db.rollback()
logger.error('TICKER(%s): error popping tasks', self.worker_name)
logger.error('%s: error popping tasks', self.worker_name)
x += 1
time.sleep(0.5)
+25 -7
View File
@@ -1623,7 +1623,7 @@ class SQLFORM(FORM):
'integer': ['=', '!=', '<', '>', '<=', '>=', 'in', 'not in'],
'double': ['=', '!=', '<', '>', '<=', '>='],
'id': ['=', '!=', '<', '>', '<=', '>=', 'in', 'not in'],
'reference': ['=', '!=', '<', '>', '<=', '>=', 'in', 'not in'],
'reference': ['=', '!='],
'boolean': ['=', '!=']}
if fields[0]._db._adapter.dbengine == 'google:datastore':
search_options['string'] = ['=', '!=', '<', '>', '<=', '>=']
@@ -1641,15 +1641,33 @@ class SQLFORM(FORM):
field.label, str) and T(field.label) or field.label
selectfields.append(OPTION(label, _value=str(field)))
operators = SELECT(*[OPTION(T(option), _value=option) for option in options])
_id = "%s_%s" % (value_id,name)
if field.type == 'boolean':
value_input = SQLFORM.widgets.boolean.widget(field,field.default,_id=_id)
elif field.type == 'double':
value_input = SQLFORM.widgets.double.widget(field,field.default,_id=_id)
elif field.type == 'time':
value_input = SQLFORM.widgets.time.widget(field,field.default,_id=_id)
elif field.type == 'date':
value_input = SQLFORM.widgets.date.widget(field,field.default,_id=_id)
elif field.type == 'datetime':
value_input = SQLFORM.widgets.datetime.widget(field,field.default,_id=_id)
elif (field.type.startswith('reference ') or
field.type.startswith('list:reference ')) and \
hasattr(field.requires,'options'):
value_input = SELECT(
OPTION(T("True"), _value="T"),
OPTION(T("False"), _value="F"),
_id="%s_%s" % (value_id,name))
*[OPTION(v, _value=k)
for k,v in field.requires.options()],
**dict(_id=_id))
elif field.type == 'integer' or \
field.type.startswith('reference ') or \
field.type.startswith('list:integer') or \
field.type.startswith('list:reference '):
value_input = SQLFORM.widgets.integer.widget(field,field.default,_id=_id)
else:
value_input = INPUT(_type='text',
_id="%s_%s" % (value_id,name),
_class=field.type)
value_input = INPUT(
_type='text', _id=_id, _class=field.type)
new_button = INPUT(
_type="button", _value=T('New'), _class="btn",
_onclick="%s_build_query('new','%s')" % (prefix,field))
+53 -46
View File
@@ -1256,6 +1256,9 @@ class Auth(object):
def navbar(self, prefix='Welcome', action=None,
separators=(' [ ', ' | ', ' ] '), user_identifier=DEFAULT,
referrer_actions=DEFAULT, mode='default'):
def Anr(*a,**b):
b['_rel']='nofollow'
return A(*a,**b)
referrer_actions = [] if not referrer_actions else referrer_actions
request = current.request
asdropdown = (mode == 'dropdown')
@@ -1286,19 +1289,19 @@ class Auth(object):
user_identifier = user_identifier % self.user
if not user_identifier:
user_identifier = ''
logout = A(T('Logout'), _href='%s/logout?_next=%s' %
logout = Anr(T('Logout'), _href='%s/logout?_next=%s' %
(action, urllib.quote(self.settings.logout_next)))
profile = A(T('Profile'), _href=href('profile'))
password = A(T('Password'), _href=href('change_password'))
profile = Anr(T('Profile'), _href=href('profile'))
password = Anr(T('Password'), _href=href('change_password'))
bar = SPAN(
prefix, user_identifier, s1, logout, s3, _class='auth_navbar')
if asdropdown:
logout = LI(A(I(_class='icon-off'), ' ' + T('Logout'), _href='%s/logout?_next=%s' %
logout = LI(Anr(I(_class='icon-off'), ' ' + T('Logout'), _href='%s/logout?_next=%s' %
(action, urllib.quote(self.settings.logout_next)))) # the space before T('Logout') is intentional. It creates a gap between icon and text
profile = LI(A(I(_class='icon-user'), ' ' +
profile = LI(Anr(I(_class='icon-user'), ' ' +
T('Profile'), _href=href('profile')))
password = LI(A(I(_class='icon-lock'), ' ' +
password = LI(Anr(I(_class='icon-lock'), ' ' +
T('Password'), _href=href('change_password')))
bar = UL(logout, _class='dropdown-menu')
# logout will be the last item in list
@@ -1312,21 +1315,21 @@ class Auth(object):
bar.insert(-1, s2)
bar.insert(-1, password)
else:
login = A(T('Login'), _href=href('login'))
register = A(T('Register'), _href=href('register'))
retrieve_username = A(
login = Anr(T('Login'), _href=href('login'))
register = Anr(T('Register'), _href=href('register'))
retrieve_username = Anr(
T('Forgot username?'), _href=href('retrieve_username'))
lost_password = A(
lost_password = Anr(
T('Lost password?'), _href=href('request_reset_password'))
bar = SPAN(s1, login, s3, _class='auth_navbar')
if asdropdown:
login = LI(A(I(_class='icon-off'), ' ' + T('Login'), _href=href('login'))) # the space before T('Login') is intentional. It creates a gap between icon and text
register = LI(A(I(_class='icon-user'),
login = LI(Anr(I(_class='icon-off'), ' ' + T('Login'), _href=href('login'))) # the space before T('Login') is intentional. It creates a gap between icon and text
register = LI(Anr(I(_class='icon-user'),
' ' + T('Register'), _href=href('register')))
retrieve_username = LI(A(I(_class='icon-edit'), ' ' + T(
retrieve_username = LI(Anr(I(_class='icon-edit'), ' ' + T(
'Forgot username?'), _href=href('retrieve_username')))
lost_password = LI(A(I(_class='icon-lock'), ' ' + T(
lost_password = LI(Anr(I(_class='icon-lock'), ' ' + T(
'Lost password?'), _href=href('request_reset_password')))
bar = UL(login, _class='dropdown-menu')
# login will be the last item in list
@@ -1349,10 +1352,10 @@ class Auth(object):
if asdropdown:
bar.insert(-1, LI('', _class='divider'))
if self.user_id:
bar = LI(A(prefix, user_identifier, _href='#'),
bar = LI(Anr(prefix, user_identifier, _href='#'),
bar, _class='dropdown')
else:
bar = LI(A(T('Login'), _href='#'),
bar = LI(Anr(T('Login'), _href='#'),
bar, _class='dropdown')
return bar
@@ -4761,7 +4764,7 @@ class Wiki(object):
Field('slug',
requires=[IS_SLUG(),
IS_NOT_IN_DB(db, 'wiki_page.slug')],
readable=False, writable=False),
writable=False),
Field('title', unique=True),
Field('body', 'text', notnull=True),
Field('tags', 'list:string'),
@@ -4970,7 +4973,7 @@ class Wiki(object):
slug.startswith(self.force_prefix)):
current.session.flash = 'slug must have "%s" prefix' \
% self.force_prefix
redirect(URL(args=('_edit', self.force_prefix + slug)))
redirect(URL(args=('_create')))
db.wiki_page.can_read.default = [Wiki.everybody]
db.wiki_page.can_edit.default = [auth.user_group_role()]
db.wiki_page.title.default = title_guess
@@ -4978,8 +4981,8 @@ class Wiki(object):
if slug == 'wiki-menu':
db.wiki_page.body.default = \
'- Menu Item > @////index\n- - Submenu > http://web2py.com'
else:
db.wiki_page.body.default = db(db.wiki_page.id==from_template).select(db.wiki_page.body)[0].body if int(from_template) > 0 else '## %s\n\npage content' % title_guess
#else:
# db.wiki_page.body.default = db(db.wiki_page.id==from_template).select(db.wiki_page.body)[0].body if int(from_template) > 0 else '## %s\n\npage content' % title_guess
vars = current.request.post_vars
if vars.body:
vars.body = vars.body.replace('://%s' % self.host, '://HOSTNAME')
@@ -4993,20 +4996,19 @@ class Wiki(object):
redirect(URL(args=slug))
script = """
$(function() {
if (!$('#wiki_page_body').length) return;
var pagecontent = $('#wiki_page_body');
if (!jQuery('#wiki_page_body').length) return;
var pagecontent = jQuery('#wiki_page_body');
pagecontent.css('font-family',
'Monaco,Menlo,Consolas,"Courier New",monospace');
var prevbutton = $('<button class="btn nopreview">Preview</button>');
var mediabutton = $('<button class="btn nopreview">Media</button>');
var preview = $('<div id="preview"></div>').hide();
var previewmedia = $('<div id="previewmedia"></div>');
var table = $('form');
var bodylabel = $('#wiki_page_body__label');
preview.insertBefore(pagecontent);
prevbutton.insertAfter(bodylabel);
mediabutton.insertBefore(table);
previewmedia.insertBefore(table);
var prevbutton = jQuery('<button class="btn nopreview">Preview</button>');
var mediabutton = jQuery('<button class="btn nopreview">Media</button>');
var preview = jQuery('<div id="preview"></div>').hide();
var previewmedia = jQuery('<div id="previewmedia"></div>');
var form = pagecontent.closest('form');
preview.insertBefore(form);
prevbutton.insertBefore(form);
mediabutton.insertBefore(form);
previewmedia.insertBefore(form);
mediabutton.toggle(function() {
web2py_component('%(urlmedia)s', 'previewmedia');
}, function() {
@@ -5017,12 +5019,12 @@ class Wiki(object):
if (prevbutton.hasClass('nopreview')) {
prevbutton.addClass('preview').removeClass(
'nopreview').html('Edit Source');
web2py_ajax_page('post', '%(url)s', {body : $('#wiki_page_body').val()}, 'preview');
pagecontent.fadeOut('fast', function() {preview.fadeIn()});
web2py_ajax_page('post', '%(url)s', {body : jQuery('#wiki_page_body').val()}, 'preview');
form.fadeOut('fast', function() {preview.fadeIn()});
} else {
prevbutton.addClass(
'nopreview').removeClass('preview').html('Preview');
preview.fadeOut('fast', function() {pagecontent.fadeIn()});
preview.fadeOut('fast', function() {form.fadeIn()});
}
})
})
@@ -5046,7 +5048,7 @@ class Wiki(object):
csv = True
create = True
if current.request.vars.embedded:
script = "var c = $('#wiki_page_body'); c.val(c.val() + $('%s').text()); return false;"
script = "var c = jQuery('#wiki_page_body'); c.val(c.val() + jQuery('%s').text()); return false;"
fragment = self.auth.db.wiki_media.id.represent
csv = False
create = False
@@ -5071,13 +5073,13 @@ class Wiki(object):
slugs=db(db.wiki_page.id>0).select(db.wiki_page.id,db.wiki_page.slug)
options=[OPTION(row.slug,_value=row.id) for row in slugs]
options.insert(0, OPTION('',_value=''))
form = SQLFORM.factory(Field("slug", default=current.request.args(1),
form = SQLFORM.factory(Field("slug", default=current.request.args(1) or self.force_prefix,
requires=(IS_SLUG(),
IS_NOT_IN_DB(db,db.wiki_page.slug))),
Field("from_template", "reference wiki_page",
requires=IS_EMPTY_OR(IS_IN_DB(db, db.wiki_page, '%(slug)s')),
comment=current.T("Choose Template or empty for new Page")),
_class="well span6")
#Field("from_template", "reference wiki_page",
# requires=IS_EMPTY_OR(IS_IN_DB(db, db.wiki_page, '%(slug)s')),
# comment=current.T("Choose Template or empty for new Page")),
_class="well span6")
form.element("[type=submit]").attributes["_value"] = current.T("Create Page from Slug")
if form.process().accepted:
@@ -5088,21 +5090,26 @@ class Wiki(object):
def pages(self):
if not self.can_manage():
return self.not_authorized()
self.auth.db.wiki_page.id.represent = lambda id, row: SPAN(
'@////%s' % row.slug)
self.auth.db.wiki_page.slug.represent = lambda slug, row: SPAN(
'@////%s' % slug)
self.auth.db.wiki_page.title.represent = lambda title, row: \
A(title, _href=URL(args=row.slug))
wiki_table = self.auth.db.wiki_page
content = SQLFORM.grid(
self.auth.db.wiki_page,
wiki_table,
fields = [wiki_table.slug,
wiki_table.title, wiki_table.tags,
wiki_table.can_read, wiki_table.can_edit],
links=[
lambda row:
A('edit', _href=URL(args=('_edit', row.slug))),
A('edit', _href=URL(args=('_edit', row.slug)),_class='btn'),
lambda row:
A('media', _href=URL(args=('_editmedia', row.slug)))],
A('media', _href=URL(args=('_editmedia', row.slug)),_class='btn')],
details=False, editable=False, deletable=False, create=False,
orderby=self.auth.db.wiki_page.title,
args=['_pages'],
user_signature=False)
return dict(content=content)
def media(self, id):
+1 -2
View File
@@ -510,8 +510,7 @@ class web2pyDialog(object):
if not options.taskbar:
thread.start_new_thread(start_browser,
(get_url(ip, proto=proto, port=port),),
dict(startup=True))
(get_url(ip, proto=proto, port=port), True))
self.password.configure(state='readonly')
[ip.configure(state='disabled') for ip in self.ips.values()]