[ADD] HORILLA BACKUP: Module for PSQL database backup to Google Drive (beta)
This commit is contained in:
1
horilla_backup/__init__.py
Normal file
1
horilla_backup/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
default_app_config = 'horilla_backup.apps.backupConfig'
|
||||
9
horilla_backup/admin.py
Normal file
9
horilla_backup/admin.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from django.contrib import admin
|
||||
from .models import *
|
||||
|
||||
# Register your models here.
|
||||
|
||||
admin.site.register(LocalBackup)
|
||||
admin.site.register(GoogleDriveBackup)
|
||||
|
||||
|
||||
15
horilla_backup/apps.py
Normal file
15
horilla_backup/apps.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
class BackupConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'horilla_backup'
|
||||
|
||||
def ready(self):
|
||||
from django.urls import include, path
|
||||
from horilla.urls import urlpatterns
|
||||
|
||||
urlpatterns.append(
|
||||
path("backup/", include("horilla_backup.urls")),
|
||||
)
|
||||
super().ready()
|
||||
|
||||
155
horilla_backup/forms.py
Normal file
155
horilla_backup/forms.py
Normal file
@@ -0,0 +1,155 @@
|
||||
from django import forms
|
||||
from .models import *
|
||||
from base.forms import ModelForm
|
||||
from django.template.loader import render_to_string
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from pathlib import Path
|
||||
from .gdrive import authenticate
|
||||
from django.core.files.storage import default_storage
|
||||
from django.core.files.base import ContentFile
|
||||
import os
|
||||
|
||||
|
||||
class LocalBackupSetupForm(ModelForm):
|
||||
verbose_name = "Server Backup"
|
||||
backup_db = forms.BooleanField(required=False, help_text="Enable to backup database to server.")
|
||||
backup_media = forms.BooleanField(required=False, help_text="Enable to backup all media files to server.")
|
||||
interval = forms.BooleanField(required=False, help_text="Enable to automate the backup in a period of seconds.")
|
||||
fixed = forms.BooleanField(required=False, help_text="Enable to automate the backup in a fixed time.")
|
||||
|
||||
class Meta:
|
||||
model = LocalBackup
|
||||
exclude = ['active']
|
||||
|
||||
|
||||
def as_p(self):
|
||||
"""
|
||||
Render the form fields as HTML table rows with Bootstrap styling.
|
||||
"""
|
||||
context = {"form": self}
|
||||
table_html = render_to_string("common_form.html", context)
|
||||
return table_html
|
||||
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
backup_db = cleaned_data.get('backup_db')
|
||||
backup_media = cleaned_data.get('backup_media')
|
||||
interval = cleaned_data.get('interval')
|
||||
fixed = cleaned_data.get('fixed')
|
||||
seconds = cleaned_data.get('seconds')
|
||||
hour = cleaned_data.get('hour')
|
||||
minute = cleaned_data.get('minute')
|
||||
backup_path = cleaned_data.get('backup_path')
|
||||
path = Path(backup_path)
|
||||
if not path.exists():
|
||||
raise ValidationError({
|
||||
'backup_path': _('The directory does not exist.')
|
||||
})
|
||||
if backup_db == False and backup_media == False:
|
||||
raise forms.ValidationError("Please select any backup option.")
|
||||
if interval == False and fixed == False:
|
||||
raise forms.ValidationError("Please select any backup automate option.")
|
||||
if interval == True and seconds == None:
|
||||
raise ValidationError({
|
||||
'seconds': _('This field is required.')
|
||||
})
|
||||
if fixed == True and hour == None:
|
||||
raise ValidationError({
|
||||
'hour': _('This field is required.')
|
||||
})
|
||||
if seconds:
|
||||
if seconds < 0:
|
||||
raise ValidationError({
|
||||
'seconds': _('Negative value is not accepatable.')
|
||||
})
|
||||
if hour:
|
||||
if hour < 0 or hour > 24:
|
||||
raise ValidationError({
|
||||
'hour': _('Enter a hour between 0 to 24.')
|
||||
})
|
||||
if minute:
|
||||
if minute < 0 or minute > 60:
|
||||
raise ValidationError({
|
||||
'minute': _('Enter a minute between 0 to 60.')
|
||||
})
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class GdriveBackupSetupForm(ModelForm):
|
||||
verbose_name = "Gdrive Backup"
|
||||
backup_db = forms.BooleanField(required=False, label="Backup DB", help_text="Enable to backup database to Gdrive")
|
||||
backup_media = forms.BooleanField(required=False, label="Backup Media", help_text="Enable to backup all media files to Gdrive")
|
||||
interval = forms.BooleanField(required=False, help_text="Enable to automate the backup in a period of seconds.")
|
||||
fixed = forms.BooleanField(required=False, help_text="Enable to automate the backup in a fixed time.")
|
||||
|
||||
class Meta:
|
||||
model = GoogleDriveBackup
|
||||
exclude = ['active']
|
||||
|
||||
|
||||
def as_p(self):
|
||||
"""
|
||||
Render the form fields as HTML table rows with Bootstrap styling.
|
||||
"""
|
||||
context = {"form": self}
|
||||
table_html = render_to_string("common_form.html", context)
|
||||
return table_html
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
backup_db = cleaned_data.get('backup_db')
|
||||
backup_media = cleaned_data.get('backup_media')
|
||||
interval = cleaned_data.get('interval')
|
||||
fixed = cleaned_data.get('fixed')
|
||||
seconds = cleaned_data.get('seconds')
|
||||
hour = cleaned_data.get('hour')
|
||||
minute = cleaned_data.get('minute')
|
||||
service_account_file = cleaned_data.get('service_account_file')
|
||||
|
||||
try:
|
||||
if GoogleDriveBackup.objects.exists():
|
||||
authenticate(service_account_file.path)
|
||||
else:
|
||||
file_data = service_account_file.read()
|
||||
# Save the processed file to the desired location
|
||||
file_name = service_account_file.name
|
||||
new_file_name = file_name
|
||||
# Save using Django's default storage system
|
||||
relative_path = default_storage.save(new_file_name, ContentFile(file_data))
|
||||
# Get the full absolute path
|
||||
full_path = default_storage.path(relative_path)
|
||||
authenticate(full_path)
|
||||
os.remove(full_path)
|
||||
|
||||
except Exception as e:
|
||||
raise forms.ValidationError("Please provide a valid service account file.")
|
||||
if backup_db == False and backup_media == False:
|
||||
raise forms.ValidationError("Please select any backup option.")
|
||||
if interval == False and fixed == False:
|
||||
raise forms.ValidationError("Please select any backup automate option.")
|
||||
if interval == True and seconds == None:
|
||||
raise ValidationError({
|
||||
'seconds': _('This field is required.')
|
||||
})
|
||||
if fixed == True and hour == None:
|
||||
raise ValidationError({
|
||||
'hour': _('This field is required.')
|
||||
})
|
||||
if seconds:
|
||||
if seconds < 0:
|
||||
raise ValidationError({
|
||||
'seconds': _('Negative value is not accepatable.')
|
||||
})
|
||||
if hour:
|
||||
if hour < 0 or hour > 24:
|
||||
raise ValidationError({
|
||||
'hour': _('Enter a hour between 0 to 24.')
|
||||
})
|
||||
if minute:
|
||||
if minute < 0 or minute > 60:
|
||||
raise ValidationError({
|
||||
'minute': _('Enter a minute between 0 to 60.')
|
||||
})
|
||||
return cleaned_data
|
||||
29
horilla_backup/gdrive.py
Normal file
29
horilla_backup/gdrive.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from googleapiclient.discovery import build
|
||||
from google.oauth2 import service_account
|
||||
from googleapiclient.http import MediaFileUpload
|
||||
import os
|
||||
|
||||
|
||||
SCOPES = ['https://www.googleapis.com/auth/drive']
|
||||
|
||||
|
||||
def authenticate(service_account_file):
|
||||
creds = service_account.Credentials.from_service_account_file(service_account_file, scopes=SCOPES)
|
||||
return creds
|
||||
|
||||
|
||||
def upload_file(file_path, service_account_file, parent_folder_id):
|
||||
creds = authenticate(service_account_file)
|
||||
service = build('drive', 'v3', credentials=creds)
|
||||
parent_folder_id = parent_folder_id
|
||||
|
||||
file_metadata = {
|
||||
'name' : os.path.basename(file_path),
|
||||
'parents' : [parent_folder_id]
|
||||
}
|
||||
media = MediaFileUpload(file_path, resumable=True)
|
||||
file = service.files().create(
|
||||
body=file_metadata,
|
||||
media_body=media,
|
||||
fields='id'
|
||||
).execute()
|
||||
18
horilla_backup/migrations/__init__.py
Normal file
18
horilla_backup/migrations/__init__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import atexit
|
||||
|
||||
|
||||
def shutdown_function():
|
||||
from horilla_backup.models import GoogleDriveBackup, LocalBackup
|
||||
if GoogleDriveBackup.objects.exists():
|
||||
google_drive_backup = GoogleDriveBackup.objects.first()
|
||||
google_drive_backup.active = False
|
||||
google_drive_backup.save()
|
||||
if LocalBackup.objects.exists():
|
||||
local_backup = LocalBackup.objects.first()
|
||||
local_backup.active = False
|
||||
local_backup.save()
|
||||
|
||||
try:
|
||||
atexit.register(shutdown_function)
|
||||
except:
|
||||
pass
|
||||
77
horilla_backup/models.py
Normal file
77
horilla_backup/models.py
Normal file
@@ -0,0 +1,77 @@
|
||||
from django.db import models
|
||||
|
||||
#Create your models here.
|
||||
|
||||
class LocalBackup(models.Model):
|
||||
backup_path = models.CharField(max_length=255, help_text="Specify the path in the server were the backup files should keep")
|
||||
backup_media = models.BooleanField(blank=True, null=True)
|
||||
backup_db = models.BooleanField(blank=True, null=True)
|
||||
interval = models.BooleanField(blank=True, null=True)
|
||||
fixed = models.BooleanField(blank=True, null=True)
|
||||
seconds = models.IntegerField(blank=True, null=True)
|
||||
hour = models.IntegerField(blank=True, null=True)
|
||||
minute = models.IntegerField(blank=True, null=True)
|
||||
active = models.BooleanField(default=False)
|
||||
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# Check if there's an existing instance
|
||||
if self.interval == False:
|
||||
self.seconds = None
|
||||
if self.fixed == False:
|
||||
self.hour = None
|
||||
self.minute = None
|
||||
if LocalBackup.objects.exists():
|
||||
# Get the existing instance
|
||||
existing_instance = LocalBackup.objects.first()
|
||||
# Update the fields of the existing instance with the new data
|
||||
for field in self._meta.fields:
|
||||
if field.name != 'id': # Avoid changing the primary key
|
||||
setattr(existing_instance, field.name, getattr(self, field.name))
|
||||
# Save the updated instance
|
||||
super(LocalBackup, existing_instance).save(*args, **kwargs)
|
||||
return existing_instance
|
||||
else:
|
||||
# If no existing instance, proceed with regular save
|
||||
super(LocalBackup, self).save(*args, **kwargs)
|
||||
return self
|
||||
|
||||
|
||||
class GoogleDriveBackup(models.Model):
|
||||
service_account_file = models.FileField(upload_to="gdrive_service_account_file",
|
||||
verbose_name="Service Account File",
|
||||
help_text="Make sure your file is in JSON format and contains your Google Service Account credentials")
|
||||
gdrive_folder_id = models.CharField(max_length=255,
|
||||
verbose_name="Gdrive Folder ID",
|
||||
help_text="Shared Gdrive folder Id with access granted to Gmail service account. Enable full permissions for seamless connection.")
|
||||
backup_media = models.BooleanField(blank=True, null=True)
|
||||
backup_db = models.BooleanField(blank=True, null=True)
|
||||
interval = models.BooleanField(blank=True, null=True)
|
||||
fixed = models.BooleanField(blank=True, null=True)
|
||||
seconds = models.IntegerField(blank=True, null=True)
|
||||
hour = models.IntegerField(blank=True, null=True)
|
||||
minute = models.IntegerField(blank=True, null=True)
|
||||
active = models.BooleanField(default=False)
|
||||
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# Check if there's an existing instance
|
||||
if self.interval == False:
|
||||
self.seconds = None
|
||||
if self.fixed == False:
|
||||
self.hour = None
|
||||
self.minute = None
|
||||
if GoogleDriveBackup.objects.exists():
|
||||
# Get the existing instance
|
||||
existing_instance = GoogleDriveBackup.objects.first()
|
||||
# Update the fields of the existing instance with the new data
|
||||
for field in self._meta.fields:
|
||||
if field.name != 'id': # Avoid changing the primary key
|
||||
setattr(existing_instance, field.name, getattr(self, field.name))
|
||||
# Save the updated instance
|
||||
super(GoogleDriveBackup, existing_instance).save(*args, **kwargs)
|
||||
return existing_instance
|
||||
else:
|
||||
# If no existing instance, proceed with regular save
|
||||
super(GoogleDriveBackup, self).save(*args, **kwargs)
|
||||
return self
|
||||
30
horilla_backup/pgdump.py
Normal file
30
horilla_backup/pgdump.py
Normal file
@@ -0,0 +1,30 @@
|
||||
import subprocess
|
||||
import os
|
||||
|
||||
def dump_postgres_db(db_name, username, output_file, password=None, host='localhost', port=5432):
|
||||
# Set environment variable for the password if provided
|
||||
if password:
|
||||
os.environ['PGPASSWORD'] = password
|
||||
|
||||
# Construct the pg_dump command
|
||||
dump_command = [
|
||||
'pg_dump',
|
||||
'-h', host,
|
||||
'-p', str(port),
|
||||
'-U', username,
|
||||
'-F', 'c', # Custom format
|
||||
'-f', output_file,
|
||||
db_name
|
||||
]
|
||||
|
||||
try:
|
||||
# Execute the pg_dump command
|
||||
result = subprocess.run(dump_command, check=True, text=True, capture_output=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
pass
|
||||
finally:
|
||||
# Clean up the environment variable
|
||||
if password:
|
||||
del os.environ['PGPASSWORD']
|
||||
|
||||
|
||||
146
horilla_backup/scheduler.py
Normal file
146
horilla_backup/scheduler.py
Normal file
@@ -0,0 +1,146 @@
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from django.core.management import call_command
|
||||
import os
|
||||
# from horilla.settings import DBBACKUP_STORAGE_OPTIONS
|
||||
from .models import *
|
||||
from .gdrive import *
|
||||
from .pgdump import *
|
||||
from horilla import settings
|
||||
from .zip import *
|
||||
|
||||
scheduler = BackgroundScheduler()
|
||||
|
||||
# def backup_database():
|
||||
# folder_path = DBBACKUP_STORAGE_OPTIONS['location']
|
||||
# local_backup = LocalBackup.objects.first()
|
||||
# if folder_path and local_backup:
|
||||
# DBBACKUP_STORAGE_OPTIONS['location'] = local_backup.backup_path
|
||||
# folder_path = DBBACKUP_STORAGE_OPTIONS['location']
|
||||
# if local_backup.backup_db:
|
||||
# call_command('dbbackup')
|
||||
# if local_backup.backup_media:
|
||||
# call_command("mediabackup")
|
||||
# files = sorted(os.listdir(folder_path), key=lambda x: os.path.getctime(os.path.join(folder_path, x)))
|
||||
|
||||
# # Remove all files except the last two
|
||||
# if len(files) > 2:
|
||||
# for file_name in files[:-2]:
|
||||
# file_path = os.path.join(folder_path, file_name)
|
||||
# if os.path.isfile(file_path):
|
||||
# try:
|
||||
# os.remove(file_path)
|
||||
# except:
|
||||
# pass
|
||||
|
||||
|
||||
# def start_backup_job():
|
||||
# """
|
||||
# Start the backup job based on the LocalBackup configuration.
|
||||
# """
|
||||
# # Check if any LocalBackup object exists
|
||||
# if LocalBackup.objects.exists():
|
||||
# local_backup = LocalBackup.objects.first()
|
||||
|
||||
# # Remove existing job if it exists
|
||||
# try:
|
||||
# scheduler.remove_job('backup_job')
|
||||
# except:
|
||||
# pass
|
||||
|
||||
# # Add new job based on LocalBackup configuration
|
||||
# if local_backup.interval:
|
||||
# scheduler.add_job(backup_database, 'interval', seconds=local_backup.seconds, id='backup_job')
|
||||
# else:
|
||||
# scheduler.add_job(backup_database, trigger='cron', hour=local_backup.hour, minute=local_backup.minute, id='backup_job')
|
||||
# # Start the scheduler if it's not already running
|
||||
# if not scheduler.running:
|
||||
# scheduler.start()
|
||||
# else:
|
||||
# stop_backup_job()
|
||||
|
||||
|
||||
# def stop_backup_job():
|
||||
# """
|
||||
# Stop the backup job if it exists.
|
||||
# """
|
||||
# try:
|
||||
# scheduler.remove_job('backup_job')
|
||||
# except:
|
||||
# pass
|
||||
|
||||
|
||||
# def restart_backup_job():
|
||||
# """
|
||||
# Restart the backup job by stopping it and starting it again.
|
||||
# """
|
||||
# stop_backup_job()
|
||||
# start_backup_job()
|
||||
|
||||
|
||||
def google_drive_backup():
|
||||
if GoogleDriveBackup.objects.exists():
|
||||
google_drive = GoogleDriveBackup.objects.first()
|
||||
service_account_file = google_drive.service_account_file.path
|
||||
gdrive_folder_id = google_drive.gdrive_folder_id
|
||||
if google_drive.backup_db:
|
||||
db = settings.DATABASES['default']
|
||||
dump_postgres_db(
|
||||
db_name=db['NAME'],
|
||||
username=db['USER'],
|
||||
output_file='backupdb.dump',
|
||||
password=db['PASSWORD']
|
||||
)
|
||||
upload_file('backupdb.dump', service_account_file, gdrive_folder_id)
|
||||
os.remove('backupdb.dump')
|
||||
if google_drive.backup_media:
|
||||
folder_to_zip = settings.MEDIA_ROOT
|
||||
output_zip_file = "media.zip"
|
||||
zip_folder(folder_to_zip, output_zip_file)
|
||||
upload_file("media.zip", service_account_file, gdrive_folder_id)
|
||||
os.remove("media.zip")
|
||||
|
||||
|
||||
def start_gdrive_backup_job():
|
||||
"""
|
||||
Start the backup job based on the LocalBackup configuration.
|
||||
"""
|
||||
# Check if any Gdrive Backup object exists
|
||||
if GoogleDriveBackup.objects.exists():
|
||||
gdrive_backup = GoogleDriveBackup.objects.first()
|
||||
|
||||
# Remove existing job if it exists
|
||||
try:
|
||||
scheduler.remove_job('backup_job')
|
||||
except:
|
||||
pass
|
||||
# Add new job based on Gdrive Backup configuration
|
||||
if gdrive_backup.interval:
|
||||
scheduler.add_job(google_drive_backup, 'interval', seconds=gdrive_backup.seconds, id='gdrive_backup_job')
|
||||
else:
|
||||
scheduler.add_job(google_drive_backup, trigger='cron', hour=gdrive_backup.hour, minute=gdrive_backup.minute, id='gdrive_backup_job')
|
||||
|
||||
# Start the scheduler if it's not already running
|
||||
if not scheduler.running:
|
||||
scheduler.start()
|
||||
|
||||
else:
|
||||
stop_gdrive_backup_job()
|
||||
|
||||
|
||||
def stop_gdrive_backup_job():
|
||||
"""
|
||||
Stop the backup job if it exists.
|
||||
"""
|
||||
try:
|
||||
scheduler.remove_job('gdrive_backup_job')
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
# def restart_gdrive_backup_job():
|
||||
# """
|
||||
# Restart the backup job by stopping it and starting it again.
|
||||
# """
|
||||
# stop_gdrive_backup_job()
|
||||
# start_gdrive_backup_job()
|
||||
|
||||
7
horilla_backup/templates/backup/404.html
Normal file
7
horilla_backup/templates/backup/404.html
Normal file
@@ -0,0 +1,7 @@
|
||||
{% extends "settings.html" %}
|
||||
{% block settings %}
|
||||
<div style="display: flex; flex-direction: column; justify-content: center; align-items: center; height: 40%;">
|
||||
<img style="display: block; width: 15%; margin: 20px auto; filter: opacity(0.5);" src="/static/images/ui/server error.png" class="" alt="Page not found. 404.">
|
||||
<h5 class="oh-404__subtitle">Only work with postgresql database.</h5>
|
||||
</div>
|
||||
{% endblock settings %}
|
||||
67
horilla_backup/templates/backup/gdrive_setup_form.html
Normal file
67
horilla_backup/templates/backup/gdrive_setup_form.html
Normal file
@@ -0,0 +1,67 @@
|
||||
{% extends "settings.html" %}
|
||||
{% block settings %}
|
||||
{% load i18n %}
|
||||
{% if form.errors %}
|
||||
<!-- form errors -->
|
||||
<div class="oh-wrapper">
|
||||
<div class="oh-alert-container">
|
||||
{% for error in form.non_field_errors %}
|
||||
<div class="oh-alert oh-alert--animated oh-alert--danger">
|
||||
{{ error }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<form method="post" action="{% url 'gdrive' %}" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{{form.as_p}}
|
||||
</form>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
|
||||
$('.oh-payslip__header').append(`<div class="d-flex">
|
||||
{% if show %}
|
||||
{% if active %}
|
||||
<a class="oh-btn oh-btn--danger mr-2" href="{% url 'gdrive_start_stop' %}" title="Stop">Stop</a>
|
||||
{% else %}
|
||||
<a class="oh-btn oh-btn--success mr-2" href="{% url 'gdrive_start_stop' %}" title="Start">Start</a>
|
||||
{% endif %}
|
||||
<a class="oh-btn oh-btn--danger-outline" href="{% url 'gdrive_delete' %}" title="Remove">
|
||||
<ion-icon name="trash-outline" role="img" class="md hydrated" aria-label="trash outline"></ion-icon>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>`)
|
||||
if (!$('#id_interval').is(':checked')) {
|
||||
$('#id_seconds').parent().hide();
|
||||
}
|
||||
if (!$('#id_fixed').is(':checked')) {
|
||||
$('#id_hour').parent().hide();``
|
||||
$('#id_minute').parent().hide();
|
||||
}
|
||||
$('#id_interval').change(function() {
|
||||
if ($(this).is(':checked')) {
|
||||
$('#id_fixed4').prop('checked', false);
|
||||
$('#id_hour').parent().hide();
|
||||
$('#id_minute').parent().hide();
|
||||
$('#id_seconds').parent().show();
|
||||
} else {
|
||||
$('#id_seconds').parent().hide();
|
||||
}
|
||||
});
|
||||
$('#id_fixed').change(function() {
|
||||
if ($(this).is(':checked')) {
|
||||
$('#id_interval').prop('checked', false);
|
||||
$('#id_seconds').parent().hide();
|
||||
$('#id_hour').parent().show();
|
||||
$('#id_minute').parent().show();
|
||||
} else {
|
||||
$('#id_hour').parent().hide();
|
||||
$('#id_minute').parent().hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock settings %}
|
||||
68
horilla_backup/templates/backup/local_setup_form.html
Normal file
68
horilla_backup/templates/backup/local_setup_form.html
Normal file
@@ -0,0 +1,68 @@
|
||||
{% extends "settings.html" %}
|
||||
{% block settings %}
|
||||
{% load i18n %}
|
||||
{% if form.errors %}
|
||||
<!-- form errors -->
|
||||
<div class="oh-wrapper">
|
||||
<div class="oh-alert-container">
|
||||
{% for error in form.non_field_errors %}
|
||||
<div class="oh-alert oh-alert--animated oh-alert--danger">
|
||||
{{ error }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<form method="post" action="{% url 'local' %}">
|
||||
{% csrf_token %}
|
||||
{{form.as_p}}
|
||||
</form>
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
$('#id_backup_path').parent().removeClass('col-md-6')
|
||||
$('.oh-payslip__header').append(`<div class="d-flex">
|
||||
{% if show %}
|
||||
{% if active %}
|
||||
<a class="oh-btn oh-btn--danger mr-2" href="{% url 'start_stop' %}" title="Stop">Stop</a>
|
||||
{% else %}
|
||||
<a class="oh-btn oh-btn--success mr-2" href="{% url 'start_stop' %}" title="Start">Start</a>
|
||||
{% endif %}
|
||||
<a class="oh-btn oh-btn--danger-outline" href="{% url 'backup_delete' %}" title="Remove">
|
||||
<ion-icon name="trash-outline" role="img" class="md hydrated" aria-label="trash outline"></ion-icon>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>`)
|
||||
if (!$('#id_interval').is(':checked')) {
|
||||
$('#id_seconds').parent().hide();
|
||||
}
|
||||
if (!$('#id_fixed').is(':checked')) {
|
||||
$('#id_hour').parent().hide();
|
||||
$('#id_minute').parent().hide();
|
||||
}
|
||||
$('#id_interval').change(function() {
|
||||
if ($(this).is(':checked')) {
|
||||
$('#id_fixed').prop('checked', false);
|
||||
$('#id_hour').parent().hide();
|
||||
$('#id_minute').parent().hide();
|
||||
$('#id_seconds').parent().show();
|
||||
|
||||
} else {
|
||||
$('#id_seconds').parent().hide();
|
||||
}
|
||||
});
|
||||
$('#id_fixed').change(function() {
|
||||
if ($(this).is(':checked')) {
|
||||
$('#id_interval').prop('checked', false);
|
||||
$('#id_seconds').parent().hide();
|
||||
$('#id_hour').parent().show();
|
||||
$('#id_minute').parent().show();
|
||||
} else {
|
||||
$('#id_hour').parent().hide();
|
||||
$('#id_minute').parent().hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock settings %}
|
||||
0
horilla_backup/tests.py
Normal file
0
horilla_backup/tests.py
Normal file
11
horilla_backup/urls.py
Normal file
11
horilla_backup/urls.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from django.urls import path
|
||||
from .views import *
|
||||
|
||||
urlpatterns = [
|
||||
# path("local/", local_setup, name="local"),
|
||||
# path("start-stop/", local_Backup_stop_or_start, name="start_stop"),
|
||||
# path("delete/", local_Backup_delete, name="backup_delete"),
|
||||
path("gdrive/", gdrive_setup, name="gdrive"),
|
||||
path("gdrive-start-stop/", gdrive_Backup_stop_or_start, name="gdrive_start_stop"),
|
||||
path("gdrive-delete/", gdrive_Backup_delete, name="gdrive_delete")
|
||||
]
|
||||
187
horilla_backup/views.py
Normal file
187
horilla_backup/views.py
Normal file
@@ -0,0 +1,187 @@
|
||||
from django.shortcuts import render, redirect
|
||||
from horilla.decorators import (
|
||||
hx_request_required,
|
||||
login_required,
|
||||
manager_can_enter,
|
||||
owner_can_enter,
|
||||
permission_required,
|
||||
)
|
||||
from .forms import *
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from .scheduler import *
|
||||
from .gdrive import *
|
||||
from .pgdump import *
|
||||
from .zip import *
|
||||
from django.db import connection
|
||||
|
||||
|
||||
# @login_required
|
||||
# @permission_required("backup.add_localbackup")
|
||||
# def local_setup(request):
|
||||
# """
|
||||
# function used to setup local backup.
|
||||
|
||||
# Parameters:
|
||||
# request (HttpRequest): The HTTP request object.
|
||||
|
||||
# Returns:
|
||||
# GET : return local backup setup template
|
||||
# POST : return settings
|
||||
# """
|
||||
# form = LocalBackupSetupForm()
|
||||
# show = False
|
||||
# active = False
|
||||
# if LocalBackup.objects.exists():
|
||||
# form = LocalBackupSetupForm(instance=LocalBackup.objects.first())
|
||||
# show = True
|
||||
# active = LocalBackup.objects.first().active
|
||||
# if request.method == "POST":
|
||||
# form = LocalBackupSetupForm(request.POST, request.FILES)
|
||||
# if form.is_valid():
|
||||
# form.save()
|
||||
# stop_backup_job()
|
||||
# messages.success(request, _("Local backup automation setup updated."))
|
||||
# return redirect("local")
|
||||
# return render(request, "backup/local_setup_form.html", {"form": form, "show":show, "active":active})
|
||||
|
||||
|
||||
# @login_required
|
||||
# @permission_required("backup.change_localbackup")
|
||||
# def local_Backup_stop_or_start(request):
|
||||
# """
|
||||
# function used to stop or start local backup.
|
||||
|
||||
# Parameters:
|
||||
# request (HttpRequest): The HTTP request object.
|
||||
|
||||
# Returns:
|
||||
# GET : return local backup setup template
|
||||
# POST : return settings
|
||||
# """
|
||||
# if LocalBackup.objects.exists():
|
||||
# local_backup = LocalBackup.objects.first()
|
||||
# if local_backup.active == True:
|
||||
# local_backup.active = False
|
||||
# stop_backup_job()
|
||||
# message = "Local Backup Automation Stopped Successfully."
|
||||
# else:
|
||||
# local_backup.active = True
|
||||
# start_backup_job()
|
||||
# message = "Local Backup Automation Started Successfully."
|
||||
# local_backup.save()
|
||||
# messages.success(request, _(message))
|
||||
# return redirect("local")
|
||||
|
||||
|
||||
# @login_required
|
||||
# @permission_required("backup.delete_localbackup")
|
||||
# def local_Backup_delete(request):
|
||||
# """
|
||||
# function used to delete local backup.
|
||||
|
||||
# Parameters:
|
||||
# request (HttpRequest): The HTTP request object.
|
||||
|
||||
# Returns:
|
||||
# GET : return local backup setup template
|
||||
# POST : return settings
|
||||
# """
|
||||
# if LocalBackup.objects.exists():
|
||||
# local_backup = LocalBackup.objects.first()
|
||||
# local_backup.delete()
|
||||
# stop_backup_job()
|
||||
# messages.success(request, _("Local Backup Automation Removed Successfully."))
|
||||
# return redirect("local")
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("backup.add_localbackup")
|
||||
def gdrive_setup(request):
|
||||
"""
|
||||
function used to setup gdrive backup.
|
||||
|
||||
Parameters:
|
||||
request (HttpRequest): The HTTP request object.
|
||||
|
||||
Returns:
|
||||
GET : return gdrive backup setup template
|
||||
POST : return gdrive backup update template
|
||||
"""
|
||||
form = GdriveBackupSetupForm()
|
||||
show = False
|
||||
active = False
|
||||
if connection.vendor != "postgresql":
|
||||
return render(request, "backup/404.html")
|
||||
if GoogleDriveBackup.objects.exists():
|
||||
instance = GoogleDriveBackup.objects.first()
|
||||
form = GdriveBackupSetupForm(instance=instance)
|
||||
show = True
|
||||
active = GoogleDriveBackup.objects.first().active
|
||||
if request.method == "POST":
|
||||
form = GdriveBackupSetupForm(request.POST, request.FILES, instance=instance)
|
||||
if form.is_valid():
|
||||
google_drive = form.save()
|
||||
google_drive.active = False
|
||||
google_drive.save()
|
||||
stop_gdrive_backup_job()
|
||||
messages.success(request, _("gdrive backup automation setup updated."))
|
||||
return redirect("gdrive")
|
||||
return render(request, "backup/gdrive_setup_form.html", {"form": form, "show":show, "active":active})
|
||||
|
||||
|
||||
if request.method == "POST":
|
||||
form = GdriveBackupSetupForm(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, _("gdrive backup automation setup Created."))
|
||||
return redirect("gdrive")
|
||||
return render(request, "backup/gdrive_setup_form.html", {"form": form, "show":show, "active":active})
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("backup.change_localbackup")
|
||||
def gdrive_Backup_stop_or_start(request):
|
||||
"""
|
||||
function used to stop or start gdrive backup.
|
||||
|
||||
Parameters:
|
||||
request (HttpRequest): The HTTP request object.
|
||||
|
||||
Returns:
|
||||
GET : return gdrive backup setup template
|
||||
POST : return gdrive backup update template
|
||||
"""
|
||||
if GoogleDriveBackup.objects.exists():
|
||||
gdive_backup = GoogleDriveBackup.objects.first()
|
||||
if gdive_backup.active == True:
|
||||
gdive_backup.active = False
|
||||
stop_gdrive_backup_job()
|
||||
message = "Gdrive Backup Automation Stopped Successfully."
|
||||
else:
|
||||
gdive_backup.active = True
|
||||
start_gdrive_backup_job()
|
||||
message = "Gdrive Backup Automation Started Successfully."
|
||||
gdive_backup.save()
|
||||
messages.success(request, _(message))
|
||||
return redirect("gdrive")
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required("backup.delete_localbackup")
|
||||
def gdrive_Backup_delete(request):
|
||||
"""
|
||||
function used to delete gdrive backup.
|
||||
|
||||
Parameters:
|
||||
request (HttpRequest): The HTTP request object.
|
||||
|
||||
Returns:
|
||||
GET : return gdrive backup setup template
|
||||
"""
|
||||
if GoogleDriveBackup.objects.exists():
|
||||
gdrive_backup = GoogleDriveBackup.objects.first()
|
||||
gdrive_backup.delete()
|
||||
stop_gdrive_backup_job()
|
||||
messages.success(request, _("Gdrive Backup Automation Removed Successfully."))
|
||||
return redirect("gdrive")
|
||||
15
horilla_backup/zip.py
Normal file
15
horilla_backup/zip.py
Normal file
@@ -0,0 +1,15 @@
|
||||
import os
|
||||
import zipfile
|
||||
|
||||
def zip_folder(folder_path, output_zip_path):
|
||||
with zipfile.ZipFile(output_zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
||||
# Walk the directory
|
||||
for root, dirs, files in os.walk(folder_path):
|
||||
for file in files:
|
||||
# Create the full filepath
|
||||
file_path = os.path.join(root, file)
|
||||
# Add file to zip, preserving the folder structure
|
||||
zipf.write(file_path, os.path.relpath(file_path, folder_path))
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user