Call it admin rather than system settings

pull/3/head
Andrew Godwin 2022-11-16 21:42:25 -07:00
rodzic 9d97fc92d8
commit 5b34ea46c3
27 zmienionych plików z 273 dodań i 111 usunięć

Wyświetl plik

@ -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),
),
]

Wyświetl plik

@ -117,7 +117,7 @@ class Post(StatorModel):
)
# 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)
published = models.DateTimeField(default=timezone.now)
@ -296,36 +296,38 @@ class Post(StatorModel):
"""
Handles an incoming create request
"""
# Ensure the Create actor is the Post's attributedTo
if data["actor"] != data["object"]["attributedTo"]:
raise ValueError("Create actor does not match its Post object", data)
# Create it
post = cls.by_ap(data["object"], create=True, update=True)
# Make timeline events for followers
for follow in Follow.objects.filter(target=post.author, source__local=True):
TimelineEvent.add_post(follow.source, post)
# Make timeline events for mentions if they're local
for mention in post.mentions.all():
if mention.local:
TimelineEvent.add_mentioned(mention, post)
# Force it into fanned_out as it's not ours
post.transition_perform(PostStates.fanned_out)
with transaction.atomic():
# Ensure the Create actor is the Post's attributedTo
if data["actor"] != data["object"]["attributedTo"]:
raise ValueError("Create actor does not match its Post object", data)
# Create it
post = cls.by_ap(data["object"], create=True, update=True)
# Make timeline events for followers
for follow in Follow.objects.filter(target=post.author, source__local=True):
TimelineEvent.add_post(follow.source, post)
# Make timeline events for mentions if they're local
for mention in post.mentions.all():
if mention.local:
TimelineEvent.add_mentioned(mention, post)
# Force it into fanned_out as it's not ours
post.transition_perform(PostStates.fanned_out)
@classmethod
def handle_delete_ap(cls, data):
"""
Handles an incoming create request
"""
# Find our post by ID if we have one
try:
post = cls.by_object_uri(data["object"]["id"])
except cls.DoesNotExist:
# It's already been deleted
return
# Ensure the actor on the request authored the post
if not post.author.actor_uri == data["actor"]:
raise ValueError("Actor on delete does not match object")
post.delete()
with transaction.atomic():
# Find our post by ID if we have one
try:
post = cls.by_object_uri(data["object"]["id"])
except cls.DoesNotExist:
# It's already been deleted
return
# Ensure the actor on the request authored the post
if not post.author.actor_uri == data["actor"]:
raise ValueError("Actor on delete does not match object")
post.delete()
def debug_fetch(self):
"""

Wyświetl plik

@ -1,6 +1,6 @@
from typing import Dict
from django.db import models
from django.db import models, transaction
from django.utils import timezone
from activities.models.fan_out import FanOut
@ -272,31 +272,33 @@ class PostInteraction(StatorModel):
"""
Handles an incoming announce/like
"""
# Create it
interaction = cls.by_ap(data, create=True)
# Boosts (announces) go to everyone who follows locally
if interaction.type == cls.Types.boost:
for follow in Follow.objects.filter(
target=interaction.identity, source__local=True
):
TimelineEvent.add_post_interaction(follow.source, interaction)
# Likes go to just the author of the post
elif interaction.type == cls.Types.like:
TimelineEvent.add_post_interaction(interaction.post.author, interaction)
# Force it into fanned_out as it's not ours
interaction.transition_perform(PostInteractionStates.fanned_out)
with transaction.atomic():
# Create it
interaction = cls.by_ap(data, create=True)
# Boosts (announces) go to everyone who follows locally
if interaction.type == cls.Types.boost:
for follow in Follow.objects.filter(
target=interaction.identity, source__local=True
):
TimelineEvent.add_post_interaction(follow.source, interaction)
# Likes go to just the author of the post
elif interaction.type == cls.Types.like:
TimelineEvent.add_post_interaction(interaction.post.author, interaction)
# Force it into fanned_out as it's not ours
interaction.transition_perform(PostInteractionStates.fanned_out)
@classmethod
def handle_undo_ap(cls, data):
"""
Handles an incoming undo for a announce/like
"""
# Find it
interaction = cls.by_ap(data["object"])
# Verify the actor matches
if data["actor"] != interaction.identity.actor_uri:
raise ValueError("Actor mismatch on interaction undo")
# Delete all events that reference it
interaction.timeline_events.all().delete()
# Force it into undone_fanned_out as it's not ours
interaction.transition_perform(PostInteractionStates.undone_fanned_out)
with transaction.atomic():
# Find it
interaction = cls.by_ap(data["object"])
# Verify the actor matches
if data["actor"] != interaction.identity.actor_uri:
raise ValueError("Actor mismatch on interaction undo")
# Delete all events that reference it
interaction.timeline_events.all().delete()
# Force it into undone_fanned_out as it's not ours
interaction.transition_perform(PostInteractionStates.undone_fanned_out)

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -1,10 +1,10 @@
from django.contrib import admin
from django.contrib import admin as djadmin
from django.urls import path
from activities.views import posts, timelines
from core import views as core
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 = [
path("", core.homepage),
@ -13,16 +13,53 @@ urlpatterns = [
path("notifications/", timelines.Notifications.as_view()),
path("local/", timelines.Local.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(
"settings/system/domains/<domain>/delete/",
settings_system.DomainDeletePage.as_view(),
"settings/",
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
path("@<handle>/", identity.ViewIdentity.as_view()),
@ -49,5 +86,5 @@ urlpatterns = [
# Task runner
path(".stator/runner/", stator.RequestRunner.as_view()),
# Django admin
path("djadmin/", admin.site.urls),
path("djadmin/", djadmin.site.urls),
]

Wyświetl plik

@ -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>

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -1,10 +1,10 @@
{% extends "base.html" %}
{% block title %}{{ section.title }} - System Settings{% endblock %}
{% block title %}{{ section.title }} - Admin{% endblock %}
{% block content %}
{% block menu %}
{% include "settings/_settings_system_menu.html" %}
{% include "admin/_menu.html" %}
{% endblock %}
<section class="icon-menu">
{% for domain in domains %}
@ -21,7 +21,7 @@
{% empty %}
<p class="option empty">You have no domains set up.</p>
{% 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
</a>
</section>

Wyświetl plik

@ -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 %}

Wyświetl plik

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

Wyświetl plik

@ -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 %}

Wyświetl plik

@ -31,11 +31,11 @@
<a href="/compose/" title="Compose" {% if top_section == "compose" %}class="selected"{% endif %}>
<i class="fa-solid fa-feather"></i> Compose
</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
</a>
{% 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
</a>
{% endif %}

Wyświetl plik

@ -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>

Wyświetl plik

@ -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 %}

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

@ -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",
),
),
]

Wyświetl plik

@ -49,10 +49,10 @@ class Domain(models.Model):
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/"
root = "/admin/domains/"
create = "/admin/domains/create/"
edit = "/admin/domains/{self.domain}/"
delete = "/admin/domains/{self.domain}/delete/"
@classmethod
def get_remote_domain(cls, domain: str) -> "Domain":

Wyświetl plik

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

Wyświetl plik

@ -10,28 +10,26 @@ from django.views.generic import FormView, RedirectView, TemplateView
from core.models import Config
from users.decorators import admin_required
from users.models import Domain
from users.models import Domain, Identity, User
@method_decorator(admin_required, name="dispatch")
class SystemSettingsRoot(RedirectView):
url = "/settings/system/basic/"
class AdminRoot(RedirectView):
pattern_name = "admin_basic"
@method_decorator(admin_required, name="dispatch")
class SystemSettingsPage(FormView):
class AdminSettingsPage(FormView):
"""
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!
"""
template_name = "settings/settings_system.html"
template_name = "admin/settings.html"
options_class = Config.SystemOptions
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 = {}
@ -84,7 +82,7 @@ class SystemSettingsPage(FormView):
return redirect(".")
class BasicPage(SystemSettingsPage):
class BasicPage(AdminSettingsPage):
section = "basic"
@ -103,7 +101,7 @@ class BasicPage(SystemSettingsPage):
@method_decorator(admin_required, name="dispatch")
class DomainsPage(TemplateView):
template_name = "settings/settings_system_domains.html"
template_name = "admin/domains.html"
def get_context_data(self):
return {
@ -115,7 +113,7 @@ class DomainsPage(TemplateView):
@method_decorator(admin_required, name="dispatch")
class DomainCreatePage(FormView):
template_name = "settings/settings_system_domain_create.html"
template_name = "admin/domain_create.html"
extra_context = {"section": "domains"}
class form_class(forms.Form):
@ -175,7 +173,7 @@ class DomainCreatePage(FormView):
@method_decorator(admin_required, name="dispatch")
class DomainEditPage(FormView):
template_name = "settings/settings_system_domain_edit.html"
template_name = "admin/domain_edit.html"
extra_context = {"section": "domains"}
class form_class(forms.Form):
@ -221,7 +219,7 @@ class DomainEditPage(FormView):
@method_decorator(admin_required, name="dispatch")
class DomainDeletePage(TemplateView):
template_name = "settings/settings_system_domain_delete.html"
template_name = "admin/domain_delete.html"
def dispatch(self, request, domain):
self.domain = get_object_or_404(
@ -241,3 +239,27 @@ class DomainDeletePage(TemplateView):
raise ValueError("Tried to delete domain with identities!")
self.domain.delete()
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",
}

Wyświetl plik

@ -3,24 +3,22 @@ from django.views.generic import RedirectView
from core.models import Config
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")
class IdentitySettingsRoot(RedirectView):
class SettingsRoot(RedirectView):
url = "/settings/interface/"
class IdentitySettingsPage(SystemSettingsPage):
class SettingsPage(AdminSettingsPage):
"""
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!
"""
extra_context = {"top_section": "settings"}
options_class = Config.IdentityOptions
template_name = "settings/settings_identity.html"
template_name = "settings/settings.html"
def load_config(self):
return Config.load_identity(self.request.identity)
@ -29,7 +27,7 @@ class IdentitySettingsPage(SystemSettingsPage):
Config.set_identity(self.request.identity, key, value)
class InterfacePage(IdentitySettingsPage):
class InterfacePage(SettingsPage):
section = "interface"