Merge github.com:web2py/web2py
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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': 'Ошибка',
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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()]
|
||||
|
||||
Reference in New Issue
Block a user