Call it admin rather than system settings

This commit is contained in:
Andrew Godwin 2022-11-16 21:42:25 -07:00
parent 9d97fc92d8
commit 5b34ea46c3
27 changed files with 273 additions and 111 deletions

View File

@ -0,0 +1,18 @@
# Generated by Django 4.1.3 on 2022-11-17 04:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("activities", "0005_post_hashtags_alter_fanout_type_and_more"),
]
operations = [
migrations.AlterField(
model_name="post",
name="hashtags",
field=models.JSONField(blank=True, null=True),
),
]

View File

@ -117,7 +117,7 @@ class Post(StatorModel):
) )
# Hashtags in the post # Hashtags in the post
hashtags = models.JSONField(default=[]) hashtags = models.JSONField(blank=True, null=True)
# When the post was originally created (as opposed to when we received it) # When the post was originally created (as opposed to when we received it)
published = models.DateTimeField(default=timezone.now) published = models.DateTimeField(default=timezone.now)
@ -296,6 +296,7 @@ class Post(StatorModel):
""" """
Handles an incoming create request Handles an incoming create request
""" """
with transaction.atomic():
# Ensure the Create actor is the Post's attributedTo # Ensure the Create actor is the Post's attributedTo
if data["actor"] != data["object"]["attributedTo"]: if data["actor"] != data["object"]["attributedTo"]:
raise ValueError("Create actor does not match its Post object", data) raise ValueError("Create actor does not match its Post object", data)
@ -316,6 +317,7 @@ class Post(StatorModel):
""" """
Handles an incoming create request Handles an incoming create request
""" """
with transaction.atomic():
# Find our post by ID if we have one # Find our post by ID if we have one
try: try:
post = cls.by_object_uri(data["object"]["id"]) post = cls.by_object_uri(data["object"]["id"])

View File

@ -1,6 +1,6 @@
from typing import Dict from typing import Dict
from django.db import models from django.db import models, transaction
from django.utils import timezone from django.utils import timezone
from activities.models.fan_out import FanOut from activities.models.fan_out import FanOut
@ -272,6 +272,7 @@ class PostInteraction(StatorModel):
""" """
Handles an incoming announce/like Handles an incoming announce/like
""" """
with transaction.atomic():
# Create it # Create it
interaction = cls.by_ap(data, create=True) interaction = cls.by_ap(data, create=True)
# Boosts (announces) go to everyone who follows locally # Boosts (announces) go to everyone who follows locally
@ -291,6 +292,7 @@ class PostInteraction(StatorModel):
""" """
Handles an incoming undo for a announce/like Handles an incoming undo for a announce/like
""" """
with transaction.atomic():
# Find it # Find it
interaction = cls.by_ap(data["object"]) interaction = cls.by_ap(data["object"])
# Verify the actor matches # Verify the actor matches

View File

@ -108,7 +108,6 @@ class Boost(View):
class Compose(FormView): class Compose(FormView):
template_name = "activities/compose.html" template_name = "activities/compose.html"
extra_context = {"top_section": "compose"}
class form_class(forms.Form): class form_class(forms.Form):
text = forms.CharField( text = forms.CharField(

View File

@ -7,4 +7,5 @@ def config_context(request):
"config_identity": ( "config_identity": (
Config.load_identity(request.identity) if request.identity else None Config.load_identity(request.identity) if request.identity else None
), ),
"top_section": request.path.strip("/").split("/")[0],
} }

View File

@ -58,6 +58,10 @@ a {
text-decoration: none; text-decoration: none;
} }
p a {
text-decoration: underline;
}
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
html:focus-within { html:focus-within {
scroll-behavior: auto; scroll-behavior: auto;

View File

@ -1,10 +1,10 @@
from django.contrib import admin from django.contrib import admin as djadmin
from django.urls import path from django.urls import path
from activities.views import posts, timelines from activities.views import posts, timelines
from core import views as core from core import views as core
from stator import views as stator from stator import views as stator
from users.views import activitypub, auth, identity, settings_identity, settings_system from users.views import activitypub, admin, auth, identity, settings
urlpatterns = [ urlpatterns = [
path("", core.homepage), path("", core.homepage),
@ -13,16 +13,53 @@ urlpatterns = [
path("notifications/", timelines.Notifications.as_view()), path("notifications/", timelines.Notifications.as_view()),
path("local/", timelines.Local.as_view()), path("local/", timelines.Local.as_view()),
path("federated/", timelines.Federated.as_view()), path("federated/", timelines.Federated.as_view()),
path("settings/", settings_identity.IdentitySettingsRoot.as_view()),
path("settings/interface/", settings_identity.InterfacePage.as_view()),
path("settings/system/", settings_system.SystemSettingsRoot.as_view()),
path("settings/system/basic/", settings_system.BasicPage.as_view()),
path("settings/system/domains/", settings_system.DomainsPage.as_view()),
path("settings/system/domains/create/", settings_system.DomainCreatePage.as_view()),
path("settings/system/domains/<domain>/", settings_system.DomainEditPage.as_view()),
path( path(
"settings/system/domains/<domain>/delete/", "settings/",
settings_system.DomainDeletePage.as_view(), settings.SettingsRoot.as_view(),
name="settings",
),
path(
"settings/interface/",
settings.InterfacePage.as_view(),
name="settings_interface",
),
path(
"admin/",
admin.AdminRoot.as_view(),
name="admin",
),
path(
"admin/basic/",
admin.BasicPage.as_view(),
name="admin_basic",
),
path(
"admin/domains/",
admin.DomainsPage.as_view(),
name="admin_domains",
),
path(
"admin/domains/create/",
admin.DomainCreatePage.as_view(),
name="admin_domains_create",
),
path(
"admin/domains/<domain>/",
admin.DomainEditPage.as_view(),
),
path(
"admin/domains/<domain>/delete/",
admin.DomainDeletePage.as_view(),
),
path(
"admin/users/",
admin.UsersPage.as_view(),
name="admin_users",
),
path(
"admin/identities/",
admin.IdentitiesPage.as_view(),
name="admin_identities",
), ),
# Identity views # Identity views
path("@<handle>/", identity.ViewIdentity.as_view()), path("@<handle>/", identity.ViewIdentity.as_view()),
@ -49,5 +86,5 @@ urlpatterns = [
# Task runner # Task runner
path(".stator/runner/", stator.RequestRunner.as_view()), path(".stator/runner/", stator.RequestRunner.as_view()),
# Django admin # Django admin
path("djadmin/", admin.site.urls), path("djadmin/", djadmin.site.urls),
] ]

View File

@ -0,0 +1,6 @@
<nav>
<a href="{% url "admin_basic" %}" {% if section == "basic" %}class="selected"{% endif %}>Basic</a>
<a href="{% url "admin_domains" %}" {% if section == "domains" %}class="selected"{% endif %}>Domains</a>
<a href="{% url "admin_users" %}" {% if section == "users" %}class="selected"{% endif %}>Users</a>
<a href="{% url "admin_identities" %}" {% if section == "identities" %}class="selected"{% endif %}>Identities</a>
</nav>

View File

@ -1,10 +1,10 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}Add Domain - System Settings{% endblock %} {% block title %}Add Domain - Admin{% endblock %}
{% block content %} {% block content %}
{% block menu %} {% block menu %}
{% include "settings/_settings_system_menu.html" %} {% include "admin/_menu.html" %}
{% endblock %} {% endblock %}
<form action="." method="POST"> <form action="." method="POST">
<h1>Add A Domain</h1> <h1>Add A Domain</h1>

View File

@ -1,10 +1,10 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}Delete {{ domain.domain }} - System Settings{% endblock %} {% block title %}Delete {{ domain.domain }} - Admin{% endblock %}
{% block content %} {% block content %}
{% block menu %} {% block menu %}
{% include "settings/_settings_system_menu.html" %} {% include "admin/_menu.html" %}
{% endblock %} {% endblock %}
<form action="." method="POST"> <form action="." method="POST">

View File

@ -1,10 +1,10 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}{{ domain.domain }} - System Settings{% endblock %} {% block title %}{{ domain.domain }} - Admin{% endblock %}
{% block content %} {% block content %}
{% block menu %} {% block menu %}
{% include "settings/_settings_system_menu.html" %} {% include "admin/_menu.html" %}
{% endblock %} {% endblock %}
<form action="." method="POST"> <form action="." method="POST">
{% csrf_token %} {% csrf_token %}

View File

@ -1,10 +1,10 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}{{ section.title }} - System Settings{% endblock %} {% block title %}{{ section.title }} - Admin{% endblock %}
{% block content %} {% block content %}
{% block menu %} {% block menu %}
{% include "settings/_settings_system_menu.html" %} {% include "admin/_menu.html" %}
{% endblock %} {% endblock %}
<section class="icon-menu"> <section class="icon-menu">
{% for domain in domains %} {% for domain in domains %}
@ -21,7 +21,7 @@
{% empty %} {% empty %}
<p class="option empty">You have no domains set up.</p> <p class="option empty">You have no domains set up.</p>
{% endfor %} {% endfor %}
<a href="/settings/system/domains/create/" class="option new"> <a href="{% url "admin_domains_create" %}" class="option new">
<i class="fa-solid fa-plus"></i> Add a domain <i class="fa-solid fa-plus"></i> Add a domain
</a> </a>
</section> </section>

View File

@ -0,0 +1,14 @@
{% extends "base.html" %}
{% block title %}Identities - Admin{% endblock %}
{% block content %}
{% block menu %}
{% include "admin/_menu.html" %}
{% endblock %}
<form>
<p>
Please use the <a href="/djadmin/users/identity/">Django Admin</a> for now.
</p>
</form>
{% endblock %}

View File

@ -1,10 +1,10 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}{{ section.title }} - System Settings{% endblock %} {% block title %}{{ section.title }} - Admin{% endblock %}
{% block content %} {% block content %}
{% block menu %} {% block menu %}
{% include "settings/_settings_system_menu.html" %} {% include "admin/_menu.html" %}
{% endblock %} {% endblock %}
<form action="." method="POST"> <form action="." method="POST">
{% csrf_token %} {% csrf_token %}

View File

@ -0,0 +1,14 @@
{% extends "base.html" %}
{% block title %}Users - Admin{% endblock %}
{% block content %}
{% block menu %}
{% include "admin/_menu.html" %}
{% endblock %}
<form>
<p>
Please use the <a href="/djadmin/users/user/">Django Admin</a> for now.
</p>
</form>
{% endblock %}

View File

@ -31,11 +31,11 @@
<a href="/compose/" title="Compose" {% if top_section == "compose" %}class="selected"{% endif %}> <a href="/compose/" title="Compose" {% if top_section == "compose" %}class="selected"{% endif %}>
<i class="fa-solid fa-feather"></i> Compose <i class="fa-solid fa-feather"></i> Compose
</a> </a>
<a href="/settings/" title="Settings" {% if top_section == "settings" %}class="selected"{% endif %}> <a href="{% url "settings" %}" title="Settings" {% if top_section == "settings" %}class="selected"{% endif %}>
<i class="fa-solid fa-gear"></i> Settings <i class="fa-solid fa-gear"></i> Settings
</a> </a>
{% if request.user.admin %} {% if request.user.admin %}
<a href="/settings/system/" title="Admin" {% if top_section == "settings_system" %}class="selected"{% endif %}> <a href="{% url "admin" %}" title="Admin" {% if top_section == "admin" %}class="selected"{% endif %}>
<i class="fa-solid fa-toolbox"></i> Admin <i class="fa-solid fa-toolbox"></i> Admin
</a> </a>
{% endif %} {% endif %}

View File

@ -1,5 +0,0 @@
<nav>
<a href="/settings/system/basic/" {% if section == "basic" %}class="selected"{% endif %}>Basic</a>
<a href="/settings/system/domains/" {% if section == "domains" %}class="selected"{% endif %}>Domains</a>
<a href="/settings/system/users/" {% if section == "users" %}class="selected"{% endif %}>Users</a>
</nav>

View File

@ -0,0 +1,18 @@
{% extends "base.html" %}
{% block title %}{{ section.title }} - Settings{% endblock %}
{% block content %}
{% block menu %}
{% include "settings/_menu.html" %}
{% endblock %}
<form action="." method="POST">
{% csrf_token %}
{% for field in form %}
{% include "forms/_field.html" %}
{% endfor %}
<div class="buttons">
<button>Save</button>
</div>
</form>
{% endblock %}

View File

@ -1,7 +0,0 @@
{% extends "settings/settings_system.html" %}
{% block title %}{{ section.title }} - Settings{% endblock %}
{% block menu %}
{% include "settings/_settings_identity_menu.html" %}
{% endblock %}

View File

@ -10,7 +10,7 @@ class DomainAdmin(admin.ModelAdmin):
@admin.register(User) @admin.register(User)
class UserAdmin(admin.ModelAdmin): class UserAdmin(admin.ModelAdmin):
pass list_display = ["email", "created", "last_seen", "admin", "moderator", "banned"]
@admin.register(UserEvent) @admin.register(UserEvent)
@ -21,6 +21,7 @@ class UserEventAdmin(admin.ModelAdmin):
@admin.register(Identity) @admin.register(Identity)
class IdentityAdmin(admin.ModelAdmin): class IdentityAdmin(admin.ModelAdmin):
list_display = ["id", "handle", "actor_uri", "state", "local"] list_display = ["id", "handle", "actor_uri", "state", "local"]
list_filter = ["local"]
raw_id_fields = ["users"] raw_id_fields = ["users"]
actions = ["force_update"] actions = ["force_update"]
readonly_fields = ["actor_json"] readonly_fields = ["actor_json"]

View File

@ -1,4 +1,6 @@
from users.models import Identity from django.utils import timezone
from users.models import Identity, User
class IdentityMiddleware: class IdentityMiddleware:
@ -17,6 +19,7 @@ class IdentityMiddleware:
else: else:
try: try:
request.identity = Identity.objects.get(id=identity_id) request.identity = Identity.objects.get(id=identity_id)
User.objects.filter(pk=request.user.pk).update(last_seen=timezone.now())
except Identity.DoesNotExist: except Identity.DoesNotExist:
request.identity = None request.identity = None

View File

@ -0,0 +1,34 @@
# Generated by Django 4.1.3 on 2022-11-17 04:18
import django.db.models.deletion
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("users", "0002_identity_public_key_id"),
]
operations = [
migrations.AddField(
model_name="user",
name="last_seen",
field=models.DateTimeField(
auto_now_add=True, default=django.utils.timezone.now
),
preserve_default=False,
),
migrations.AlterField(
model_name="identity",
name="domain",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.PROTECT,
related_name="identities",
to="users.domain",
),
),
]

View File

@ -49,10 +49,10 @@ class Domain(models.Model):
updated = models.DateTimeField(auto_now=True) updated = models.DateTimeField(auto_now=True)
class urls(urlman.Urls): class urls(urlman.Urls):
root = "/settings/system/domains/" root = "/admin/domains/"
create = "/settings/system/domains/create/" create = "/admin/domains/create/"
edit = "/settings/system/domains/{self.domain}/" edit = "/admin/domains/{self.domain}/"
delete = "/settings/system/domains/{self.domain}/delete/" delete = "/admin/domains/{self.domain}/delete/"
@classmethod @classmethod
def get_remote_domain(cls, domain: str) -> "Domain": def get_remote_domain(cls, domain: str) -> "Domain":

View File

@ -38,6 +38,7 @@ class User(AbstractBaseUser):
created = models.DateTimeField(auto_now_add=True) created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True) updated = models.DateTimeField(auto_now=True)
last_seen = models.DateTimeField(auto_now_add=True)
USERNAME_FIELD = "email" USERNAME_FIELD = "email"
EMAIL_FIELD = "email" EMAIL_FIELD = "email"

View File

@ -10,28 +10,26 @@ from django.views.generic import FormView, RedirectView, TemplateView
from core.models import Config from core.models import Config
from users.decorators import admin_required from users.decorators import admin_required
from users.models import Domain from users.models import Domain, Identity, User
@method_decorator(admin_required, name="dispatch") @method_decorator(admin_required, name="dispatch")
class SystemSettingsRoot(RedirectView): class AdminRoot(RedirectView):
url = "/settings/system/basic/" pattern_name = "admin_basic"
@method_decorator(admin_required, name="dispatch") @method_decorator(admin_required, name="dispatch")
class SystemSettingsPage(FormView): class AdminSettingsPage(FormView):
""" """
Shows a settings page dynamically created from our settings layout Shows a settings page dynamically created from our settings layout
at the bottom of the page. Don't add this to a URL directly - subclass! at the bottom of the page. Don't add this to a URL directly - subclass!
""" """
template_name = "settings/settings_system.html" template_name = "admin/settings.html"
options_class = Config.SystemOptions options_class = Config.SystemOptions
section: ClassVar[str] section: ClassVar[str]
options: Dict[str, Dict[str, str]] options: Dict[str, Dict[str, str]]
extra_context = {"top_section": "settings_system"}
def get_form_class(self): def get_form_class(self):
# Create the fields dict from the config object # Create the fields dict from the config object
fields = {} fields = {}
@ -84,7 +82,7 @@ class SystemSettingsPage(FormView):
return redirect(".") return redirect(".")
class BasicPage(SystemSettingsPage): class BasicPage(AdminSettingsPage):
section = "basic" section = "basic"
@ -103,7 +101,7 @@ class BasicPage(SystemSettingsPage):
@method_decorator(admin_required, name="dispatch") @method_decorator(admin_required, name="dispatch")
class DomainsPage(TemplateView): class DomainsPage(TemplateView):
template_name = "settings/settings_system_domains.html" template_name = "admin/domains.html"
def get_context_data(self): def get_context_data(self):
return { return {
@ -115,7 +113,7 @@ class DomainsPage(TemplateView):
@method_decorator(admin_required, name="dispatch") @method_decorator(admin_required, name="dispatch")
class DomainCreatePage(FormView): class DomainCreatePage(FormView):
template_name = "settings/settings_system_domain_create.html" template_name = "admin/domain_create.html"
extra_context = {"section": "domains"} extra_context = {"section": "domains"}
class form_class(forms.Form): class form_class(forms.Form):
@ -175,7 +173,7 @@ class DomainCreatePage(FormView):
@method_decorator(admin_required, name="dispatch") @method_decorator(admin_required, name="dispatch")
class DomainEditPage(FormView): class DomainEditPage(FormView):
template_name = "settings/settings_system_domain_edit.html" template_name = "admin/domain_edit.html"
extra_context = {"section": "domains"} extra_context = {"section": "domains"}
class form_class(forms.Form): class form_class(forms.Form):
@ -221,7 +219,7 @@ class DomainEditPage(FormView):
@method_decorator(admin_required, name="dispatch") @method_decorator(admin_required, name="dispatch")
class DomainDeletePage(TemplateView): class DomainDeletePage(TemplateView):
template_name = "settings/settings_system_domain_delete.html" template_name = "admin/domain_delete.html"
def dispatch(self, request, domain): def dispatch(self, request, domain):
self.domain = get_object_or_404( self.domain = get_object_or_404(
@ -241,3 +239,27 @@ class DomainDeletePage(TemplateView):
raise ValueError("Tried to delete domain with identities!") raise ValueError("Tried to delete domain with identities!")
self.domain.delete() self.domain.delete()
return redirect("/settings/system/domains/") return redirect("/settings/system/domains/")
@method_decorator(admin_required, name="dispatch")
class UsersPage(TemplateView):
template_name = "admin/users.html"
def get_context_data(self):
return {
"users": User.objects.order_by("email"),
"section": "users",
}
@method_decorator(admin_required, name="dispatch")
class IdentitiesPage(TemplateView):
template_name = "admin/identities.html"
def get_context_data(self):
return {
"identities": Identity.objects.order_by("username"),
"section": "identities",
}

View File

@ -3,24 +3,22 @@ from django.views.generic import RedirectView
from core.models import Config from core.models import Config
from users.decorators import identity_required from users.decorators import identity_required
from users.views.settings_system import SystemSettingsPage from users.views.admin import AdminSettingsPage
@method_decorator(identity_required, name="dispatch") @method_decorator(identity_required, name="dispatch")
class IdentitySettingsRoot(RedirectView): class SettingsRoot(RedirectView):
url = "/settings/interface/" url = "/settings/interface/"
class IdentitySettingsPage(SystemSettingsPage): class SettingsPage(AdminSettingsPage):
""" """
Shows a settings page dynamically created from our settings layout Shows a settings page dynamically created from our settings layout
at the bottom of the page. Don't add this to a URL directly - subclass! at the bottom of the page. Don't add this to a URL directly - subclass!
""" """
extra_context = {"top_section": "settings"}
options_class = Config.IdentityOptions options_class = Config.IdentityOptions
template_name = "settings/settings_identity.html" template_name = "settings/settings.html"
def load_config(self): def load_config(self):
return Config.load_identity(self.request.identity) return Config.load_identity(self.request.identity)
@ -29,7 +27,7 @@ class IdentitySettingsPage(SystemSettingsPage):
Config.set_identity(self.request.identity, key, value) Config.set_identity(self.request.identity, key, value)
class InterfacePage(IdentitySettingsPage): class InterfacePage(SettingsPage):
section = "interface" section = "interface"