summaryrefslogtreecommitdiffstats
path: root/users
diff options
context:
space:
mode:
authorAndrew Godwin2022-11-16 21:12:28 -0700
committerAndrew Godwin2022-11-16 21:12:28 -0700
commit1b52acdb56346d939eb2e26ff449697b52fa7142 (patch)
tree222e5a3de47db230c392f18ab983abe69eb94c5d /users
parent44af0d4c59eed1c3715e9044e75c159cfddf54cc (diff)
downloadtakahe-1b52acdb56346d939eb2e26ff449697b52fa7142.tar.gz
takahe-1b52acdb56346d939eb2e26ff449697b52fa7142.tar.bz2
takahe-1b52acdb56346d939eb2e26ff449697b52fa7142.zip
Domains management pages
Diffstat (limited to 'users')
-rw-r--r--users/models/domain.py7
-rw-r--r--users/models/identity.py1
-rw-r--r--users/views/settings_identity.py2
-rw-r--r--users/views/settings_system.py148
4 files changed, 156 insertions, 2 deletions
diff --git a/users/models/domain.py b/users/models/domain.py
index d2b17e2..af0bbab 100644
--- a/users/models/domain.py
+++ b/users/models/domain.py
@@ -1,5 +1,6 @@
from typing import Optional
+import urlman
from django.db import models
@@ -47,6 +48,12 @@ class Domain(models.Model):
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
+ class urls(urlman.Urls):
+ root = "/settings/system/domains/"
+ create = "/settings/system/domains/create/"
+ edit = "/settings/system/domains/{self.domain}/"
+ delete = "/settings/system/domains/{self.domain}/delete/"
+
@classmethod
def get_remote_domain(cls, domain: str) -> "Domain":
return cls.objects.get_or_create(domain=domain, local=False)[0]
diff --git a/users/models/identity.py b/users/models/identity.py
index 15caef4..d97f5f0 100644
--- a/users/models/identity.py
+++ b/users/models/identity.py
@@ -67,6 +67,7 @@ class Identity(StatorModel):
blank=True,
null=True,
on_delete=models.PROTECT,
+ related_name="identities",
)
name = models.CharField(max_length=500, blank=True, null=True)
diff --git a/users/views/settings_identity.py b/users/views/settings_identity.py
index 8c52f9e..f35928a 100644
--- a/users/views/settings_identity.py
+++ b/users/views/settings_identity.py
@@ -17,6 +17,8 @@ class IdentitySettingsPage(SystemSettingsPage):
at the bottom of the page. Don't add this to a URL directly - subclass!
"""
+ extra_context = {"top_section": "settings"}
+
options_class = Config.IdentityOptions
template_name = "settings/settings_identity.html"
diff --git a/users/views/settings_system.py b/users/views/settings_system.py
index 52ba349..bfd9fb7 100644
--- a/users/views/settings_system.py
+++ b/users/views/settings_system.py
@@ -1,13 +1,16 @@
+import re
from functools import partial
from typing import ClassVar, Dict
from django import forms
-from django.shortcuts import redirect
+from django.db import models
+from django.shortcuts import get_object_or_404, redirect
from django.utils.decorators import method_decorator
-from django.views.generic import FormView, RedirectView
+from django.views.generic import FormView, RedirectView, TemplateView
from core.models import Config
from users.decorators import identity_required
+from users.models import Domain
@method_decorator(identity_required, name="dispatch")
@@ -27,6 +30,8 @@ class SystemSettingsPage(FormView):
section: ClassVar[str]
options: Dict[str, Dict[str, str]]
+ extra_context = {"top_section": "settings_system"}
+
def get_form_class(self):
# Create the fields dict from the config object
fields = {}
@@ -93,3 +98,142 @@ class BasicPage(SystemSettingsPage):
"help_text": "Used for logo background and other highlights",
},
}
+
+
+class DomainsPage(TemplateView):
+
+ template_name = "settings/settings_system_domains.html"
+
+ def get_context_data(self):
+ return {
+ "domains": Domain.objects.filter(local=True).order_by("domain"),
+ "section": "domains",
+ }
+
+
+class DomainCreatePage(FormView):
+
+ template_name = "settings/settings_system_domain_create.html"
+ extra_context = {"section": "domains"}
+
+ class form_class(forms.Form):
+ domain = forms.CharField(
+ help_text="The domain displayed as part of a user's identity.\nCannot be changed after the domain has been created.",
+ )
+ service_domain = forms.CharField(
+ help_text="Optional - a domain that serves Takahē if it is not running on the main domain.\nCannot be changed after the domain has been created.",
+ required=False,
+ )
+ public = forms.BooleanField(
+ help_text="If any user on this server can create identities here",
+ widget=forms.Select(choices=[(True, "Public"), (False, "Private")]),
+ required=False,
+ )
+
+ domain_regex = re.compile(
+ r"^((?!-))(xn--)?[a-z0-9][a-z0-9-_]{0,61}[a-z0-9]{0,1}\.(xn--)?([a-z0-9\-]{1,61}|[a-z0-9-]{1,30}\.[a-z]{2,})$"
+ )
+
+ def clean_domain(self):
+ if not self.domain_regex.match(self.cleaned_data["domain"]):
+ raise forms.ValidationError("This does not look like a domain name")
+ if Domain.objects.filter(
+ models.Q(domain=self.cleaned_data["domain"])
+ | models.Q(service_domain=self.cleaned_data["domain"])
+ ):
+ raise forms.ValidationError("This domain name is already in use")
+ return self.cleaned_data["domain"]
+
+ def clean_service_domain(self):
+ if not self.cleaned_data["service_domain"]:
+ return None
+ if not self.domain_regex.match(self.cleaned_data["service_domain"]):
+ raise forms.ValidationError("This does not look like a domain name")
+ if Domain.objects.filter(
+ models.Q(domain=self.cleaned_data["service_domain"])
+ | models.Q(service_domain=self.cleaned_data["service_domain"])
+ ):
+ raise forms.ValidationError("This domain name is already in use")
+ if self.cleaned_data.get("domain") == self.cleaned_data["service_domain"]:
+ raise forms.ValidationError(
+ "You cannot have the domain and service domain be the same (did you mean to leave service domain blank?)"
+ )
+ return self.cleaned_data["service_domain"]
+
+ def form_valid(self, form):
+ Domain.objects.create(
+ domain=form.cleaned_data["domain"],
+ service_domain=form.cleaned_data["service_domain"] or None,
+ public=form.cleaned_data["public"],
+ local=True,
+ )
+ return redirect(Domain.urls.root)
+
+
+class DomainEditPage(FormView):
+
+ template_name = "settings/settings_system_domain_edit.html"
+ extra_context = {"section": "domains"}
+
+ class form_class(forms.Form):
+ domain = forms.CharField(
+ help_text="The domain displayed as part of a user's identity.\nCannot be changed after the domain has been created.",
+ disabled=True,
+ )
+ service_domain = forms.CharField(
+ help_text="Optional - a domain that serves Takahē if it is not running on the main domain.\nCannot be changed after the domain has been created.",
+ disabled=True,
+ required=False,
+ )
+ public = forms.BooleanField(
+ help_text="If any user on this server can create identities here",
+ widget=forms.Select(choices=[(True, "Public"), (False, "Private")]),
+ required=False,
+ )
+
+ def dispatch(self, request, domain):
+ self.domain = get_object_or_404(
+ Domain.objects.filter(local=True), domain=domain
+ )
+ return super().dispatch(request)
+
+ def get_context_data(self):
+ context = super().get_context_data()
+ context["domain"] = self.domain
+ return context
+
+ def form_valid(self, form):
+ self.domain.public = form.cleaned_data["public"]
+ self.domain.save()
+ return redirect(Domain.urls.root)
+
+ def get_initial(self):
+ return {
+ "domain": self.domain.domain,
+ "service_domain": self.domain.service_domain,
+ "public": self.domain.public,
+ }
+
+
+class DomainDeletePage(TemplateView):
+
+ template_name = "settings/settings_system_domain_delete.html"
+
+ def dispatch(self, request, domain):
+ self.domain = get_object_or_404(
+ Domain.objects.filter(public=True), domain=domain
+ )
+ return super().dispatch(request)
+
+ def get_context_data(self):
+ return {
+ "domain": self.domain,
+ "num_identities": self.domain.identities.count(),
+ "section": "domains",
+ }
+
+ def post(self, request):
+ if self.domain.identities.exists():
+ raise ValueError("Tried to delete domain with identities!")
+ self.domain.delete()
+ return redirect("/settings/system/domains/")