|
@ -83,6 +83,15 @@ class DbBackup(models.Model): |
|
|
"read permissions for that file.", |
|
|
"read permissions for that file.", |
|
|
) |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
backup_format = fields.Selection( |
|
|
|
|
|
[ |
|
|
|
|
|
("zip", "zip (includes filestore)"), |
|
|
|
|
|
("dump", "pg_dump custom format (without filestore)") |
|
|
|
|
|
], |
|
|
|
|
|
default='zip', |
|
|
|
|
|
help="Choose the format for this backup." |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
@api.model |
|
|
@api.model |
|
|
def _default_folder(self): |
|
|
def _default_folder(self): |
|
|
"""Default to ``backups`` folder inside current server datadir.""" |
|
|
"""Default to ``backups`` folder inside current server datadir.""" |
|
@ -131,11 +140,11 @@ class DbBackup(models.Model): |
|
|
def action_backup(self): |
|
|
def action_backup(self): |
|
|
"""Run selected backups.""" |
|
|
"""Run selected backups.""" |
|
|
backup = None |
|
|
backup = None |
|
|
filename = self.filename(datetime.now()) |
|
|
|
|
|
successful = self.browse() |
|
|
successful = self.browse() |
|
|
|
|
|
|
|
|
# Start with local storage |
|
|
# Start with local storage |
|
|
for rec in self.filtered(lambda r: r.method == "local"): |
|
|
for rec in self.filtered(lambda r: r.method == "local"): |
|
|
|
|
|
filename = self.filename(datetime.now(), ext=rec.backup_format) |
|
|
with rec.backup_log(): |
|
|
with rec.backup_log(): |
|
|
# Directory must exist |
|
|
# Directory must exist |
|
|
try: |
|
|
try: |
|
@ -151,21 +160,28 @@ class DbBackup(models.Model): |
|
|
shutil.copyfileobj(cached, destiny) |
|
|
shutil.copyfileobj(cached, destiny) |
|
|
# Generate new backup |
|
|
# Generate new backup |
|
|
else: |
|
|
else: |
|
|
db.dump_db(self.env.cr.dbname, destiny) |
|
|
|
|
|
|
|
|
db.dump_db( |
|
|
|
|
|
self.env.cr.dbname, |
|
|
|
|
|
destiny, |
|
|
|
|
|
backup_format=rec.backup_format |
|
|
|
|
|
) |
|
|
backup = backup or destiny.name |
|
|
backup = backup or destiny.name |
|
|
successful |= rec |
|
|
successful |= rec |
|
|
|
|
|
|
|
|
# Ensure a local backup exists if we are going to write it remotely |
|
|
# Ensure a local backup exists if we are going to write it remotely |
|
|
sftp = self.filtered(lambda r: r.method == "sftp") |
|
|
sftp = self.filtered(lambda r: r.method == "sftp") |
|
|
if sftp: |
|
|
if sftp: |
|
|
if backup: |
|
|
|
|
|
cached = open(backup) |
|
|
|
|
|
else: |
|
|
|
|
|
cached = db.dump_db(self.env.cr.dbname, None) |
|
|
|
|
|
|
|
|
|
|
|
with cached: |
|
|
|
|
|
for rec in sftp: |
|
|
for rec in sftp: |
|
|
|
|
|
filename = self.filename(datetime.now(), ext=rec.backup_format) |
|
|
with rec.backup_log(): |
|
|
with rec.backup_log(): |
|
|
|
|
|
|
|
|
|
|
|
cached = db.dump_db( |
|
|
|
|
|
self.env.cr.dbname, |
|
|
|
|
|
None, |
|
|
|
|
|
backup_format=rec.backup_format |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
with cached: |
|
|
with rec.sftp_connection() as remote: |
|
|
with rec.sftp_connection() as remote: |
|
|
# Directory must exist |
|
|
# Directory must exist |
|
|
try: |
|
|
try: |
|
@ -255,13 +271,16 @@ class DbBackup(models.Model): |
|
|
self.name) |
|
|
self.name) |
|
|
|
|
|
|
|
|
@staticmethod |
|
|
@staticmethod |
|
|
def filename(when): |
|
|
|
|
|
|
|
|
def filename(when, ext='zip'): |
|
|
"""Generate a file name for a backup. |
|
|
"""Generate a file name for a backup. |
|
|
|
|
|
|
|
|
:param datetime.datetime when: |
|
|
:param datetime.datetime when: |
|
|
Use this datetime instead of :meth:`datetime.datetime.now`. |
|
|
Use this datetime instead of :meth:`datetime.datetime.now`. |
|
|
|
|
|
:param str ext: Extension of the file. Default: dump.zip |
|
|
""" |
|
|
""" |
|
|
return "{:%Y_%m_%d_%H_%M_%S}.dump.zip".format(when) |
|
|
|
|
|
|
|
|
return "{:%Y_%m_%d_%H_%M_%S}.{ext}".format( |
|
|
|
|
|
when, ext='dump.zip' if ext == 'zip' else ext |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
@api.multi |
|
|
@api.multi |
|
|
def sftp_connection(self): |
|
|
def sftp_connection(self): |
|
|