forked from Fediversity/Fediversity
Compare commits
23 commits
Author | SHA1 | Date | |
---|---|---|---|
1076552f75 | |||
4273c7a608 | |||
a088d25eb7 | |||
b3b525dee4 | |||
8fabd7f0f1 | |||
0f2c5390d6 | |||
fb5e8ae453 | |||
014c3efc70 | |||
1ebea118cc | |||
99b8fe8870 | |||
d32e11389c | |||
624e354da5 | |||
7c8c6c7eb9 | |||
da2e7d93a9 | |||
a97418c30e | |||
af1c051498 | |||
c8062a87d4 | |||
2fb0ccf0aa | |||
46107cb21d | |||
17570c6434 | |||
bb12139f5a | |||
66e88325ec | |||
![]() |
4c1aa34d97 |
10 changed files with 270 additions and 1 deletions
panel/src/panel
1
panel/src/panel/admin.py
Normal file
1
panel/src/panel/admin.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
|
47
panel/src/panel/forms.py
Normal file
47
panel/src/panel/forms.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
from django import forms
|
||||||
|
# from django.forms import inlineformset_factory
|
||||||
|
from panel.models import (
|
||||||
|
Configuration,
|
||||||
|
PeertubeConfig,
|
||||||
|
PixelfedConfig,
|
||||||
|
MastodonConfig,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Deployment(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Configuration
|
||||||
|
fields = [
|
||||||
|
'domain',
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
operator = self.instance.operator.username
|
||||||
|
self.fields['domain'].choices = [
|
||||||
|
(code, f"{operator}.{label}")
|
||||||
|
for code, label in self.instance._meta.get_field('domain').choices
|
||||||
|
]
|
||||||
|
model = Configuration
|
||||||
|
fields = ['domain']
|
||||||
|
|
||||||
|
|
||||||
|
class MastodonConfigForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = MastodonConfig
|
||||||
|
fields = ['enable']
|
||||||
|
|
||||||
|
|
||||||
|
class PixelfedConfigForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = PixelfedConfig
|
||||||
|
fields = ['enable']
|
||||||
|
|
||||||
|
|
||||||
|
class PeertubeConfigForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = PeertubeConfig
|
||||||
|
fields = ['enable']
|
||||||
|
|
||||||
|
# BookInlineFormSet = inlineformset_factory(Deployment, MastodonConfigForm, extra=1, can_delete=False)
|
50
panel/src/panel/migrations/0001_initial.py
Normal file
50
panel/src/panel/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
# Generated by Django 4.2.16 on 2025-03-04 07:55
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='MastodonConfig',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('enable', models.BooleanField(default=False)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PeertubeConfig',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('enable', models.BooleanField(default=False)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PixelfedConfig',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('enable', models.BooleanField(default=False)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Configuration',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('enable', models.BooleanField(default=False, help_text='Enable the configuration')),
|
||||||
|
('domain', models.CharField(choices=[('fediversity_eu', 'fediversity.eu'), ('fediversity_net', 'fediversity.net')], max_length=255)),
|
||||||
|
('mastodon', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='config', to='panel.mastodonconfig')),
|
||||||
|
('operator', models.ForeignKey(help_text='Operator who owns the configuration', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='configurations', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('peertube', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='config', to='panel.peertubeconfig')),
|
||||||
|
('pixelfed', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='config', to='panel.pixelfedconfig')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
0
panel/src/panel/migrations/__init__.py
Normal file
0
panel/src/panel/migrations/__init__.py
Normal file
95
panel/src/panel/models.py
Normal file
95
panel/src/panel/models.py
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
from django.db import models
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
|
||||||
|
class MastodonConfig(models.Model):
|
||||||
|
enable = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Mastodon: {self.enable}"
|
||||||
|
|
||||||
|
|
||||||
|
class PixelfedConfig(models.Model):
|
||||||
|
enable = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Pixelfed: {self.enable}"
|
||||||
|
|
||||||
|
|
||||||
|
class PeertubeConfig(models.Model):
|
||||||
|
enable = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Peertube: {self.enable}"
|
||||||
|
|
||||||
|
|
||||||
|
class Configuration(models.Model):
|
||||||
|
class ConfigurationManager(models.Manager):
|
||||||
|
# Define which related fields should be auto-created
|
||||||
|
auto_create_related = [
|
||||||
|
'mastodon',
|
||||||
|
'pixelfed',
|
||||||
|
'peertube',
|
||||||
|
]
|
||||||
|
|
||||||
|
def create(self, **kwargs):
|
||||||
|
# Create the model instance but don't save yet
|
||||||
|
instance = self.model(**kwargs)
|
||||||
|
|
||||||
|
# Auto-create any missing related objects
|
||||||
|
for field_name in self.auto_create_related:
|
||||||
|
# Skip if the field was provided
|
||||||
|
if field_name in kwargs:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Get the related model class
|
||||||
|
field = self.model._meta.get_field(field_name)
|
||||||
|
related_model = field.related_model
|
||||||
|
|
||||||
|
# Create the related object
|
||||||
|
related_obj = related_model.objects.create()
|
||||||
|
|
||||||
|
# Set the relation
|
||||||
|
setattr(instance, field_name, related_obj)
|
||||||
|
|
||||||
|
# Save and return the instance
|
||||||
|
instance.save()
|
||||||
|
return instance
|
||||||
|
|
||||||
|
operator = models.ForeignKey(
|
||||||
|
User,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
null=True,
|
||||||
|
related_name="configurations",
|
||||||
|
help_text="Operator who owns the configuration",
|
||||||
|
)
|
||||||
|
enable = models.BooleanField(
|
||||||
|
default=False,
|
||||||
|
help_text="Enable the configuration",
|
||||||
|
)
|
||||||
|
domain = models.CharField(
|
||||||
|
# XXX: hard-code available apex domains for now,
|
||||||
|
# they will be prefixed by the user name
|
||||||
|
# TODO: map to user's registered domains
|
||||||
|
choices=[
|
||||||
|
("fediversity_eu", "fediversity.eu"),
|
||||||
|
("fediversity_net", "fediversity.net")
|
||||||
|
],
|
||||||
|
max_length=255,
|
||||||
|
)
|
||||||
|
mastodon = models.OneToOneField(MastodonConfig, on_delete=models.CASCADE, related_name='config', null=True)
|
||||||
|
pixelfed = models.OneToOneField(PixelfedConfig, on_delete=models.CASCADE, related_name='config', null=True)
|
||||||
|
peertube = models.OneToOneField(PeertubeConfig, on_delete=models.CASCADE, related_name='config', null=True)
|
||||||
|
|
||||||
|
# Use the custom manager
|
||||||
|
# objects = ConfigurationManager()
|
||||||
|
|
||||||
|
# def save(self, *args, **kwargs):
|
||||||
|
# # Create the related items if they don't exist
|
||||||
|
# if not hasattr(self, 'mastodon'):
|
||||||
|
# MastodonConfig.objects.create(config=self)
|
||||||
|
# if not hasattr(self, 'mastodon'):
|
||||||
|
# MastodonConfig.objects.create(config=self)
|
||||||
|
# if not hasattr(self, 'mastodon'):
|
||||||
|
# MastodonConfig.objects.create(config=self)
|
||||||
|
# super().save(*args, **kwargs)
|
|
@ -26,6 +26,9 @@
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'service_list' %}">Services</a>
|
<a href="{% url 'service_list' %}">Services</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'configuration_form' %}">Configuration</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
{% load custom_tags %}
|
{% load custom_tags %}
|
||||||
<li>
|
<li>
|
||||||
|
|
13
panel/src/panel/templates/configuration_form.html
Normal file
13
panel/src/panel/templates/configuration_form.html
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<form method="post" enctype="multipart/form-data" action="{% url 'configuration_form' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.as_p }}
|
||||||
|
|
||||||
|
<h2>Services</h2>
|
||||||
|
{{ children.as_p }}
|
||||||
|
|
||||||
|
<button class="button" disabled>Deploy</button>
|
||||||
|
<button class="button" type="submit" >Save</button>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
|
@ -3,5 +3,4 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Fediversity Panel</h1>
|
<h1>Fediversity Panel</h1>
|
||||||
|
|
||||||
<p>Hello world!</p>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -25,4 +25,5 @@ urlpatterns = [
|
||||||
path("", include("django.contrib.auth.urls")),
|
path("", include("django.contrib.auth.urls")),
|
||||||
path("account/", views.AccountDetail.as_view(), name='account_detail'),
|
path("account/", views.AccountDetail.as_view(), name='account_detail'),
|
||||||
path("services/", views.ServiceList.as_view(), name='service_list'),
|
path("services/", views.ServiceList.as_view(), name='service_list'),
|
||||||
|
path("configuration/", views.ConfigurationForm.as_view(), name='configuration_form'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -2,15 +2,75 @@ from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
from django.views.generic import DetailView
|
from django.views.generic import DetailView
|
||||||
|
from django.views.generic.edit import UpdateView
|
||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
from panel.models import Configuration
|
||||||
|
from panel import forms
|
||||||
|
from .models import (
|
||||||
|
Configuration,
|
||||||
|
MastodonConfig,
|
||||||
|
PixelfedConfig,
|
||||||
|
PeertubeConfig,
|
||||||
|
)
|
||||||
|
from .forms import (
|
||||||
|
Deployment,
|
||||||
|
MastodonConfigForm,
|
||||||
|
PixelfedConfigForm,
|
||||||
|
PeertubeConfigForm,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Index(TemplateView):
|
class Index(TemplateView):
|
||||||
template_name = 'index.html'
|
template_name = 'index.html'
|
||||||
|
|
||||||
|
|
||||||
class AccountDetail(LoginRequiredMixin, DetailView):
|
class AccountDetail(LoginRequiredMixin, DetailView):
|
||||||
model = User
|
model = User
|
||||||
template_name = 'account_detail.html'
|
template_name = 'account_detail.html'
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
return self.request.user
|
return self.request.user
|
||||||
|
|
||||||
|
|
||||||
class ServiceList(TemplateView):
|
class ServiceList(TemplateView):
|
||||||
template_name = 'service_list.html'
|
template_name = 'service_list.html'
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigurationForm(LoginRequiredMixin, UpdateView):
|
||||||
|
template_name = 'configuration_form.html'
|
||||||
|
model = Configuration
|
||||||
|
form_class = forms.Deployment
|
||||||
|
success_url = reverse_lazy('configuration_form')
|
||||||
|
|
||||||
|
def get_object(self, queryset=None):
|
||||||
|
obj, created = Configuration.objects.get_or_create(
|
||||||
|
operator=self.request.user)
|
||||||
|
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
deploy_form = self.get_object()
|
||||||
|
|
||||||
|
context.update({
|
||||||
|
'mastodon_form': MastodonConfigForm(self.request.POST or None, instance=deploy_form.mastodon),
|
||||||
|
'pixelfed_form': PixelfedConfigForm(self.request.POST or None, instance=deploy_form.pixelfed),
|
||||||
|
'peertube_form': PeertubeConfigForm(self.request.POST or None, instance=deploy_form.peertube),
|
||||||
|
})
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
response = super().form_valid(form) # Save main Configuration form
|
||||||
|
obj = self.get_object() # Get instance
|
||||||
|
|
||||||
|
# Save related forms
|
||||||
|
MastodonConfigForm(self.request.POST,
|
||||||
|
instance=obj.mastodon).save()
|
||||||
|
PixelfedConfigForm(self.request.POST,
|
||||||
|
instance=obj.pixelfed).save()
|
||||||
|
PeertubeConfigForm(self.request.POST,
|
||||||
|
instance=obj.peertube).save()
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
Loading…
Add table
Reference in a new issue