initial commit
This commit is contained in:
commit
164fa61ad2
40 changed files with 1263 additions and 0 deletions
0
community_backup/webui/__init__.py
Normal file
0
community_backup/webui/__init__.py
Normal file
7
community_backup/webui/admin.py
Normal file
7
community_backup/webui/admin.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from .models import BorgRepository, Voucher
|
||||
|
||||
# Register your models here.
|
||||
admin.site.register(BorgRepository)
|
||||
admin.site.register(Voucher)
|
||||
5
community_backup/webui/apps.py
Normal file
5
community_backup/webui/apps.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class WebuiConfig(AppConfig):
|
||||
name = "webui"
|
||||
157
community_backup/webui/deployments.py
Normal file
157
community_backup/webui/deployments.py
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
from pathlib import Path
|
||||
from subprocess import run
|
||||
from collections import defaultdict
|
||||
from typing import Any
|
||||
from logging import getLogger
|
||||
import shutil
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from .models import BorgRepository
|
||||
|
||||
logger = getLogger("deployments")
|
||||
|
||||
# sync users
|
||||
|
||||
|
||||
def get_users(user_prefix: str = "u-") -> dict[str, dict[str, Any]]:
|
||||
|
||||
passwd = Path("/etc/passwd").read_text()
|
||||
output = dict()
|
||||
for line in passwd.splitlines():
|
||||
name, _, _, _, _, homedir, _ = line.split(":")
|
||||
if not name.startswith(user_prefix):
|
||||
continue
|
||||
|
||||
output[name] = {"homedir": Path(homedir)}
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def cleanup_users(
|
||||
usernames: set[str],
|
||||
system_users: dict[str, dict[str, Any]],
|
||||
dry_run=False,
|
||||
):
|
||||
"""Cleans up all unused user accounts and directories on the system.
|
||||
|
||||
usernames is the set of users that should exist.
|
||||
system_users contains all the users that exist (with the correct prefix)"""
|
||||
|
||||
base_dir = Path("/home/")
|
||||
|
||||
# cleanup users
|
||||
for user in system_users.keys():
|
||||
if user not in usernames:
|
||||
logger.info("Deleting user '%(user)s'.")
|
||||
print(f"Deleting user '{user}'.")
|
||||
if not dry_run:
|
||||
run(["userdel", user])
|
||||
pass
|
||||
|
||||
# cleanup directories
|
||||
for dir in base_dir.iterdir():
|
||||
if dir.is_dir() and dir.name.startswith("u-") and dir.name not in usernames:
|
||||
logger.info("Deleting home directory '%(dir)s'")
|
||||
print(f"Deleting home directory '{dir}'")
|
||||
if not dry_run:
|
||||
shutil.rmtree(dir)
|
||||
pass
|
||||
|
||||
|
||||
def create_users(
|
||||
usernames: set[str],
|
||||
system_users: dict[str, dict[str, Any]],
|
||||
dry_run=False,
|
||||
):
|
||||
|
||||
for username in usernames:
|
||||
if username not in system_users.keys():
|
||||
logger.info("Adding user '%(username)s'")
|
||||
print(f"Adding user '{username}'")
|
||||
if not dry_run:
|
||||
add_user(username)
|
||||
|
||||
|
||||
def add_user(username: str):
|
||||
|
||||
users = get_users()
|
||||
assert username not in users.keys(), "User already exists"
|
||||
|
||||
run(["useradd", "--no-user-group", "--create-home", username], check=True)
|
||||
|
||||
|
||||
def sync_users(dry_run=False):
|
||||
|
||||
# gather data
|
||||
users = User.objects.all()
|
||||
usernames = {f"u-{user.pk}" for user in users}
|
||||
system_users = get_users()
|
||||
|
||||
# clean up old users
|
||||
cleanup_users(usernames=usernames, system_users=system_users, dry_run=dry_run)
|
||||
|
||||
# create the new users
|
||||
create_users(usernames=usernames, system_users=system_users, dry_run=dry_run)
|
||||
|
||||
for user in users:
|
||||
username = f"u-{user.pk}"
|
||||
sync_repos(
|
||||
username=username,
|
||||
user=user,
|
||||
system_user=system_users[username],
|
||||
dry_run=dry_run,
|
||||
)
|
||||
|
||||
|
||||
# sync authorized_keys
|
||||
def sync_repos(username: str, user: User, system_user: dict[str, Any], dry_run=False):
|
||||
|
||||
repos = BorgRepository.objects.filter(user=user)
|
||||
|
||||
repos_by_key = defaultdict(list)
|
||||
for repo in repos:
|
||||
repos_by_key[repo.key].append(repo)
|
||||
|
||||
homedir = system_user["homedir"]
|
||||
|
||||
# create .ssh directory
|
||||
ssh_dir = homedir / ".ssh"
|
||||
ssh_dir.mkdir(
|
||||
mode=0o750,
|
||||
exist_ok=True,
|
||||
parents=True,
|
||||
)
|
||||
|
||||
# create authorized_keys file
|
||||
authorized_keys = ssh_dir / "authorized_keys"
|
||||
|
||||
commands = []
|
||||
for key, repos in repos_by_key.items():
|
||||
repo_paths = [
|
||||
f"--restrict-to-repository {str(system_user['homedir'] / repo.name)}"
|
||||
for repo in repos
|
||||
]
|
||||
commands.append(
|
||||
f"""command="borg serve {" ".join(repo_paths)} --quota=500G",restrict ssh-rsa {key}"""
|
||||
)
|
||||
|
||||
print("\n".join(commands))
|
||||
|
||||
authorized_keys.write_text("\n".join(commands) + "\n")
|
||||
|
||||
# remove repositories that do no longer exist
|
||||
repo_names = {repo.name for repo in repos}
|
||||
print(repo_names)
|
||||
for dir in homedir.iterdir():
|
||||
print(dir.name)
|
||||
if dir.is_dir() and not dir.name.startswith("."):
|
||||
if dir.name not in repo_names:
|
||||
print(f"removing unused repo '{dir}'")
|
||||
shutil.rmtree(dir)
|
||||
|
||||
# create the repositories
|
||||
for repo in repos:
|
||||
path = system_user["homedir"] / repo.name
|
||||
path.mkdir(mode=0o750, exist_ok=True, parents=True)
|
||||
shutil.chown(path, user=username)
|
||||
40
community_backup/webui/forms.py
Normal file
40
community_backup/webui/forms.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
from .models import BorgRepository
|
||||
from django import forms
|
||||
from django.contrib.auth.forms import UserCreationForm
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from .models import Voucher
|
||||
|
||||
|
||||
class BorgRepositoryForm(forms.ModelForm):
|
||||
class Meta:
|
||||
fields = ["name", "key", "user"]
|
||||
model = BorgRepository
|
||||
widgets = {"user": forms.HiddenInput()}
|
||||
|
||||
|
||||
class RegisterUserForm(UserCreationForm):
|
||||
email = forms.EmailField()
|
||||
voucher = forms.CharField(help_text="You registration voucher.")
|
||||
|
||||
def clean_voucher(self):
|
||||
obj = Voucher.objects.filter(code=self.cleaned_data["voucher"], used=False)
|
||||
|
||||
if not obj.exists():
|
||||
raise ValidationError(
|
||||
"Voucher code '%(code)s' is invalid",
|
||||
code="invalid",
|
||||
params={"code": self.cleaned_data["voucher"]},
|
||||
)
|
||||
|
||||
return obj.first()
|
||||
|
||||
def save(self, commit=True):
|
||||
voucher = self.cleaned_data["voucher"]
|
||||
|
||||
voucher.used = True
|
||||
voucher.save()
|
||||
super().save(commit)
|
||||
|
||||
class Meta(UserCreationForm.Meta):
|
||||
fields = ("username", "email")
|
||||
0
community_backup/webui/management/__init__.py
Normal file
0
community_backup/webui/management/__init__.py
Normal file
0
community_backup/webui/management/commands/__init__.py
Normal file
0
community_backup/webui/management/commands/__init__.py
Normal file
17
community_backup/webui/management/commands/sync_users.py
Normal file
17
community_backup/webui/management/commands/sync_users.py
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
from django.core.management.base import BaseCommand, CommandError
|
||||
from ...models import BorgRepository
|
||||
from ...deployments import sync_users
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Synchronized users on the host with the database."
|
||||
|
||||
def handle(self, *args, **options):
|
||||
|
||||
repos = BorgRepository.objects.all()
|
||||
|
||||
for repo in repos:
|
||||
self.stdout.write(f"{repo}")
|
||||
self.stdout.write(self.style.SUCCESS("This is a test"))
|
||||
|
||||
sync_users(dry_run=False)
|
||||
40
community_backup/webui/migrations/0001_initial.py
Normal file
40
community_backup/webui/migrations/0001_initial.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# Generated by Django 6.0.3 on 2026-03-23 06:14
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="BorgRepository",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=100)),
|
||||
("key", models.TextField()),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
# Generated by Django 6.0.3 on 2026-03-28 06:56
|
||||
|
||||
import django.core.validators
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("webui", "0001_initial"),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="borgrepository",
|
||||
name="key",
|
||||
field=models.TextField(
|
||||
validators=[
|
||||
django.core.validators.RegexValidator(
|
||||
"(ssh\\-rsa|ecdsa\\-sha2\\-nistp256|ssh\\-ed25519) ([a-zA-Z0-9\\+/]+) (\\S*)",
|
||||
message="not a valid SSH public key.",
|
||||
)
|
||||
]
|
||||
),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name="borgrepository",
|
||||
constraint=models.UniqueConstraint(
|
||||
fields=("name", "user"), name="BorgRepository_name_user_unique"
|
||||
),
|
||||
),
|
||||
]
|
||||
29
community_backup/webui/migrations/0003_voucher.py
Normal file
29
community_backup/webui/migrations/0003_voucher.py
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# Generated by Django 6.0.3 on 2026-03-28 18:59
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("webui", "0002_alter_borgrepository_key_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Voucher",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("used", models.BooleanField(default=False)),
|
||||
("code", models.CharField(max_length=100, unique=True)),
|
||||
],
|
||||
),
|
||||
]
|
||||
0
community_backup/webui/migrations/__init__.py
Normal file
0
community_backup/webui/migrations/__init__.py
Normal file
59
community_backup/webui/models.py
Normal file
59
community_backup/webui/models.py
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.validators import RegexValidator
|
||||
|
||||
|
||||
# Create your models here.
|
||||
class BorgRepository(models.Model):
|
||||
name = models.CharField(
|
||||
max_length=100,
|
||||
validators=[
|
||||
RegexValidator(
|
||||
r"[a-zA-Z0-9\-_]+", message="Only a-z, A-Z, 0-9, - and _ are allowed."
|
||||
)
|
||||
],
|
||||
)
|
||||
key = models.TextField(
|
||||
validators=[
|
||||
RegexValidator(
|
||||
r"(ssh\-rsa|ecdsa\-sha2\-nistp256|ssh\-ed25519) ([a-zA-Z0-9\+/]+) (\S*)",
|
||||
message="not a valid SSH public key.",
|
||||
),
|
||||
]
|
||||
)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
|
||||
class Meta:
|
||||
constraints = [
|
||||
models.UniqueConstraint(
|
||||
fields=("name", "user"), name="BorgRepository_name_user_unique"
|
||||
)
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f"BorgRepository '{self.user.username}' - '{self.name}' ({self.pk})"
|
||||
|
||||
@property
|
||||
def username(self) -> str:
|
||||
"""Returns the username of the linux user account for this user."""
|
||||
return f"u-{self.pk}"
|
||||
|
||||
def save(self):
|
||||
from .tasks import update_user
|
||||
|
||||
update_user.enqueue(user_pk=self.user.pk)
|
||||
super().save()
|
||||
|
||||
def delete(self):
|
||||
from .tasks import update_user
|
||||
|
||||
super().delete()
|
||||
update_user.enqueue(user_pk=self.user.pk)
|
||||
|
||||
|
||||
class Voucher(models.Model):
|
||||
used = models.BooleanField(default=False)
|
||||
code = models.CharField(max_length=100, unique=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"Voucher '{self.code}'"
|
||||
12
community_backup/webui/tasks.py
Normal file
12
community_backup/webui/tasks.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
from django.tasks import task
|
||||
from .deployments import sync_repos, get_users
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
|
||||
@task
|
||||
def update_user(user_pk):
|
||||
user = User.objects.get(pk=user_pk)
|
||||
username = f"u-{user.pk}"
|
||||
system_user = get_users()[username]
|
||||
|
||||
sync_repos(username=username, user=user, system_user=system_user)
|
||||
6
community_backup/webui/templates/about.md
Normal file
6
community_backup/webui/templates/about.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# About Community Backup
|
||||
|
||||
Community Backup is a service that provides backup space free of charge.
|
||||
|
||||
* This is
|
||||
* a list
|
||||
62
community_backup/webui/templates/base.html
Normal file
62
community_backup/webui/templates/base.html
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Community Backup</title>
|
||||
</head>
|
||||
<body style="display: flex; flex-direction: column; height: 100vh">
|
||||
<header class="bg-body-tertiary">
|
||||
<div class="container">
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="/" >Community Backup</a>
|
||||
<div class="navbar-nav">
|
||||
{% if request.user.is_authenticated %}
|
||||
<a class="nav-link" href="{% url 'borg_list' %}">Borg Repositories</a>
|
||||
<div class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
{{ user.username }}
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><form method=post action="{% url 'logout' %}?next={% url 'landing' %}">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn dropdown-item">Logout</button>
|
||||
</form></li>
|
||||
<li><a class="dropdown-item" href="{% url 'password_change' %}">Change Password</a></li>
|
||||
{% if user.is_staff %}
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="{% url 'admin:index' %}">Admin</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
{% else %}
|
||||
<a class="btn nav-item" type="button" href="{% url 'login' %}?next={% url 'landing' %}">Login</a>
|
||||
<a class="btn nav-item" type="button" href="{% url 'register' %}">Register</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
<main class="container" style="flex: 1 1 auto">
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</main>
|
||||
<footer class="bg-body-tertiary">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<ul>
|
||||
<li>
|
||||
<a href="{% url "about" %}">About</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">Imprint</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
|
||||
</body>
|
||||
</html>
|
||||
7
community_backup/webui/templates/borg_edit.html
Normal file
7
community_backup/webui/templates/borg_edit.html
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<form action="{% url borg_edit %}">
|
||||
{{ form }}
|
||||
</form>
|
||||
{% endblock %}
|
||||
28
community_backup/webui/templates/borg_list.html
Normal file
28
community_backup/webui/templates/borg_list.html
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex">
|
||||
<div class="p-2 ms-auto">
|
||||
<a class="btn btn-primary" href="{% url 'borg_add' %}" role="button">Add Repository</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<th>Name</th>
|
||||
<th>Key</th>
|
||||
<th></th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for repo in object_list %}
|
||||
<tr>
|
||||
<td>{{ repo.name }}</td>
|
||||
<td>{{ repo.key }}</td>
|
||||
<td><a href="{% url 'borg_update' pk=repo.pk %}" class="btn btn-warning btn-sm">Edit</a>
|
||||
<a href="{% url 'borg_delete' pk=repo.pk %}" class="btn btn-danger btn-sm">Delete</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
||||
7
community_backup/webui/templates/landing.html
Normal file
7
community_backup/webui/templates/landing.html
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<row class="text-center p-2">
|
||||
<h1>Welcome to Community Backup!</h1>
|
||||
</row>
|
||||
{% endblock %}
|
||||
1
community_backup/webui/templates/login.html
Normal file
1
community_backup/webui/templates/login.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
Hello at the login template
|
||||
7
community_backup/webui/templates/markdown.html
Normal file
7
community_backup/webui/templates/markdown.html
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<row class="p-2">
|
||||
{{ markdown_content|safe }}
|
||||
</row>
|
||||
{% endblock %}
|
||||
28
community_backup/webui/templates/register.html
Normal file
28
community_backup/webui/templates/register.html
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
{% extends "base.html" %}
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if form.errors %}
|
||||
{% for error in form.errors %}
|
||||
<p>{{ error }}</p>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if next %}
|
||||
{% if user.is_authenticated %}
|
||||
<p>Your account doesn't have access to this page. To proceed,
|
||||
please login with an account that has access.</p>
|
||||
{% else %}
|
||||
<p>Please login to see this page.</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="{% url 'register' %}">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
<input class="btn btn-primary" type="submit" value="Register">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
26
community_backup/webui/templates/registration/login.html
Normal file
26
community_backup/webui/templates/registration/login.html
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{% extends "base.html" %}
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% if form.errors %}
|
||||
<p>Your username and password didn't match. Please try again.</p>
|
||||
{% endif %}
|
||||
|
||||
{% if next %}
|
||||
{% if user.is_authenticated %}
|
||||
<p>Your account doesn't have access to this page. To proceed,
|
||||
please login with an account that has access.</p>
|
||||
{% else %}
|
||||
<p>Please login to see this page.</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="{% url 'login' %}">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
<input class="btn btn-primary" type="submit" value="login">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
{% extends "base.html" %}
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">{% csrf_token %}
|
||||
<p>Are you sure you want to delete "{{ object.name }}"?</p>
|
||||
{{ form }}
|
||||
<input class="btn btn-danger" type="submit" value="Confirm">
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% load crispy_forms_tags %}
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
{% if form.initial %}
|
||||
<input class="btn btn-primary" type="submit" value="Save">
|
||||
{% else %}
|
||||
<input class="btn btn-primary" type="submit" value="Add">
|
||||
{% endif %}
|
||||
</form>
|
||||
{% endblock %}
|
||||
12
community_backup/webui/templatetags.py
Normal file
12
community_backup/webui/templatetags.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
from django import template
|
||||
from django.template.defaultfilters import stringfilter
|
||||
|
||||
import markdown2 as md
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter()
|
||||
@stringfilter
|
||||
def markdown(value):
|
||||
return md.markdown(value, extensions=["markdown.extensions.fenced_code"])
|
||||
3
community_backup/webui/tests.py
Normal file
3
community_backup/webui/tests.py
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
21
community_backup/webui/urls.py
Normal file
21
community_backup/webui/urls.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path("", views.LandingView.as_view(), name="landing"),
|
||||
path("borg/", views.BorgView.as_view(), name="borg_list"),
|
||||
path("borg/add/", views.BorgRepositoryCreateView.as_view(), name="borg_add"),
|
||||
path(
|
||||
"borg/update/<int:pk>/",
|
||||
views.BorgRepositoryUpdateView.as_view(),
|
||||
name="borg_update",
|
||||
),
|
||||
path(
|
||||
"borg/delete/<int:pk>/",
|
||||
views.BorgRepositoryDeleteView.as_view(),
|
||||
name="borg_delete",
|
||||
),
|
||||
path("register/", views.RegisterUserView.as_view(), name="register"),
|
||||
path("about/", views.AboutView.as_view(), name="about"),
|
||||
]
|
||||
93
community_backup/webui/views.py
Normal file
93
community_backup/webui/views.py
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
from django.shortcuts import get_object_or_404
|
||||
from django.views.generic.base import TemplateView
|
||||
from django.views.generic import ListView, FormView
|
||||
from django.views.generic.edit import CreateView, UpdateView, DeleteView
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.forms import BaseUserCreationForm
|
||||
from django.contrib.auth.mixins import UserPassesTestMixin, LoginRequiredMixin
|
||||
|
||||
from django.template.loader import get_template
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
import markdown2
|
||||
|
||||
from .models import BorgRepository, Voucher
|
||||
from .forms import BorgRepositoryForm, RegisterUserForm
|
||||
|
||||
|
||||
class UserOwnsRepositoryMixin(UserPassesTestMixin):
|
||||
def test_func(self):
|
||||
repo = get_object_or_404(BorgRepository, pk=self.kwargs["pk"])
|
||||
return repo.user == self.request.user
|
||||
|
||||
|
||||
class LoginView(TemplateView):
|
||||
template_name = "login.html"
|
||||
|
||||
|
||||
class LandingView(TemplateView):
|
||||
template_name = "landing.html"
|
||||
|
||||
|
||||
class MarkdownView(TemplateView):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
source = get_template(self.md).template.source
|
||||
context["markdown_content"] = markdown2.markdown(source)
|
||||
return context
|
||||
|
||||
|
||||
class AboutView(MarkdownView):
|
||||
template_name = "markdown.html"
|
||||
md = "about.md"
|
||||
|
||||
|
||||
class BorgView(LoginRequiredMixin, ListView):
|
||||
template_name = "borg_list.html"
|
||||
content_object_name = "borg_repositories"
|
||||
|
||||
def get_queryset(self):
|
||||
data = BorgRepository.objects.filter(user=self.request.user)
|
||||
return data
|
||||
|
||||
|
||||
class RegisterUserView(FormView):
|
||||
template_name = "register.html"
|
||||
form_class = RegisterUserForm
|
||||
success_url = "/"
|
||||
|
||||
def form_valid(self, form):
|
||||
form.save()
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class BorgRepositoryCreateView(LoginRequiredMixin, CreateView):
|
||||
model = BorgRepository
|
||||
success_url = "/borg/"
|
||||
form_class = BorgRepositoryForm
|
||||
|
||||
def get_initial(self):
|
||||
return {"user": self.request.user}
|
||||
|
||||
def form_valid(self, form):
|
||||
form.instance.user = self.request.user
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class BorgRepositoryDeleteView(LoginRequiredMixin, UserOwnsRepositoryMixin, DeleteView):
|
||||
model = BorgRepository
|
||||
success_url = "/borg/"
|
||||
|
||||
|
||||
class BorgRepositoryUpdateView(LoginRequiredMixin, UserOwnsRepositoryMixin, UpdateView):
|
||||
model = BorgRepository
|
||||
success_url = "/borg/"
|
||||
form_class = BorgRepositoryForm
|
||||
|
||||
def form_valid(self, form):
|
||||
form.instance.user = self.request.user
|
||||
return super().form_valid(form)
|
||||
Loading…
Add table
Add a link
Reference in a new issue