User admin and LD schema fixes

This commit is contained in:
Andrew Godwin 2022-12-16 16:38:52 -07:00
parent 1bcdff79e7
commit 45c6978bc3
13 changed files with 189 additions and 23 deletions

View file

@ -659,7 +659,7 @@ class Post(StatorModel):
if update or created: if update or created:
post.content = data["content"] post.content = data["content"]
post.summary = data.get("summary") post.summary = data.get("summary")
post.sensitive = data.get("as:sensitive", False) post.sensitive = data.get("sensitive", False)
post.url = data.get("url") post.url = data.get("url")
post.published = parse_ld_date(data.get("published")) post.published = parse_ld_date(data.get("published"))
post.edited = parse_ld_date(data.get("updated")) post.edited = parse_ld_date(data.get("updated"))
@ -670,7 +670,7 @@ class Post(StatorModel):
if tag["type"].lower() == "mention": if tag["type"].lower() == "mention":
mention_identity = Identity.by_actor_uri(tag["href"], create=True) mention_identity = Identity.by_actor_uri(tag["href"], create=True)
post.mentions.add(mention_identity) post.mentions.add(mention_identity)
elif tag["type"].lower() == "as:hashtag": elif tag["type"].lower() == "hashtag":
post.hashtags.append(tag["name"].lower().lstrip("#")) post.hashtags.append(tag["name"].lower().lstrip("#"))
elif tag["type"].lower() == "http://joinmastodon.org/ns#emoji": elif tag["type"].lower() == "http://joinmastodon.org/ns#emoji":
emoji = Emoji.by_ap_tag(post.author.domain, tag, create=True) emoji = Emoji.by_ap_tag(post.author.domain, tag, create=True)

View file

@ -415,6 +415,7 @@ def canonicalise(json_data: dict, include_security: bool = False) -> dict:
"votersCount": "toot:votersCount", "votersCount": "toot:votersCount",
"Hashtag": "as:Hashtag", "Hashtag": "as:Hashtag",
"Public": "as:Public", "Public": "as:Public",
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
}, },
] ]
if include_security: if include_security:

View file

@ -96,9 +96,14 @@ urlpatterns = [
), ),
path( path(
"admin/users/", "admin/users/",
admin.Users.as_view(), admin.UsersRoot.as_view(),
name="admin_users", name="admin_users",
), ),
path(
"admin/users/<id>/",
admin.UserEdit.as_view(),
name="admin_user_edit",
),
path( path(
"admin/identities/", "admin/identities/",
admin.Identities.as_view(), admin.Identities.as_view(),

View file

@ -0,0 +1,36 @@
{% extends "settings/base.html" %}
{% block subtitle %}{{ user.email }}{% endblock %}
{% block content %}
<h1>{{ editing_user.email }}</h1>
<form action="." method="POST">
{% csrf_token %}
<fieldset>
<legend>Permissions</legend>
{% include "forms/_field.html" with field=form.status %}
{% if same_user %}
<ul class="errorlist">
<li>You cannot edit your own permission status!</li>
</ul>
{% endif %}
</fieldset>
<fieldset>
<legend>Identities</legend>
{% for identity in editing_user.identities.all %}
{% include "activities/_identity.html" %}
{% empty %}
<p>This user has no identities yet.</p>
{% endfor %}
</fieldset>
<fieldset>
<legend>Dates</legend>
<p>Last seen: <time title="{{ editing_user.last_seen }} UTC">{{ editing_user.last_seen | timesince }} ago</time></p>
<p>Created: <time title="{{ editing_user.created }} UTC">{{ editing_user.created | timesince }} ago</time></p>
</fieldset>
<div class="buttons">
<a href="{{ editing_user.urls.admin }}" class="button secondary left">Back</a>
<button>Save</button>
</div>
</form>
{% endblock %}

View file

@ -3,7 +3,40 @@
{% block subtitle %}Users{% endblock %} {% block subtitle %}Users{% endblock %}
{% block content %} {% block content %}
<p> <form action="." class="search">
Please use the <a href="/djadmin/users/user/">Django Admin</a> for now. <input type="search" name="query" value="{{ query }}" placeholder="Search by email">
</p> <button><i class="fa-solid fa-search"></i></button>
</form>
<section class="icon-menu">
{% for user in page_obj %}
<a class="option" href="{{ user.urls.admin_edit }}">
<i class="fa-solid fa-user"></i>
<span class="handle">
{{ user.email }}
<small>
{{ user.num_identities }} identit{{ user.num_identities|pluralize:"y,ies" }}
</small>
</span>
{% if user.banned %}
<span class="pill bad">Banned</span>
{% endif %}
</a>
{% empty %}
<p class="option empty">
{% if query %}
No users match your query.
{% else %}
There are no users yet.
{% endif %}
</p>
{% endfor %}
<div class="load-more">
{% if page_obj.has_previous %}
<a class="button" href=".?page={{ page_obj.previous_page_number }}">Previous Page</a>
{% endif %}
{% if page_obj.has_next %}
<a class="button" href=".?page={{ page_obj.next_page_number }}">Next Page</a>
{% endif %}
</div>
</section>
{% endblock %} {% endblock %}

View file

@ -155,7 +155,7 @@ def test_fetch_actor(httpx_mock, config_system):
"mediaType": "image/jpeg", "mediaType": "image/jpeg",
"url": "https://example.com/image.jpg", "url": "https://example.com/image.jpg",
}, },
"as:manuallyApprovesFollowers": False, "manuallyApprovesFollowers": False,
"name": "Test User", "name": "Test User",
"preferredUsername": "test", "preferredUsername": "test",
"published": "2022-11-02T00:00:00Z", "published": "2022-11-02T00:00:00Z",

View file

@ -89,7 +89,13 @@ class PasswordResetAdmin(admin.ModelAdmin):
@admin.register(InboxMessage) @admin.register(InboxMessage)
class InboxMessageAdmin(admin.ModelAdmin): class InboxMessageAdmin(admin.ModelAdmin):
list_display = ["id", "state", "state_changed", "message_type", "message_actor"] list_display = [
"id",
"state",
"state_changed",
"message_type_full",
"message_actor",
]
list_filter = ("state",) list_filter = ("state",)
search_fields = ["message"] search_fields = ["message"]
actions = ["reset_state"] actions = ["reset_state"]

View file

@ -438,7 +438,7 @@ class Identity(StatorModel):
self.username = self.username["@value"] self.username = self.username["@value"]
if self.username: if self.username:
self.username = self.username.lower() self.username = self.username.lower()
self.manually_approves_followers = document.get("as:manuallyApprovesFollowers") self.manually_approves_followers = document.get("manuallyApprovesFollowers")
self.public_key = document.get("publicKey", {}).get("publicKeyPem") self.public_key = document.get("publicKey", {}).get("publicKeyPem")
self.public_key_id = document.get("publicKey", {}).get("id") self.public_key_id = document.get("publicKey", {}).get("id")
self.icon_uri = document.get("icon", {}).get("url") self.icon_uri = document.get("icon", {}).get("url")

View file

@ -115,6 +115,13 @@ class InboxMessage(StatorModel):
def message_object_type(self): def message_object_type(self):
return self.message["object"]["type"].lower() return self.message["object"]["type"].lower()
@property
def message_type_full(self):
if isinstance(self.message.get("object"), dict):
return f"{self.message_type}.{self.message_object_type}"
else:
return f"{self.message_type}"
@property @property
def message_actor(self): def message_actor(self):
return self.message.get("actor") return self.message.get("actor")

View file

@ -48,7 +48,7 @@ class SystemActor:
}, },
"preferredUsername": self.username, "preferredUsername": self.username,
"url": self.profile_uri, "url": self.profile_uri,
"as:manuallyApprovesFollowers": True, "manuallyApprovesFollowers": True,
"publicKey": { "publicKey": {
"id": self.public_key_id, "id": self.public_key_id,
"owner": self.actor_uri, "owner": self.actor_uri,

View file

@ -1,3 +1,4 @@
import urlman
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
from django.db import models from django.db import models
@ -44,6 +45,10 @@ class User(AbstractBaseUser):
objects = UserManager() objects = UserManager()
class urls(urlman.Urls):
admin = "/admin/users/"
admin_edit = "{admin}{self.pk}/"
@property @property
def is_active(self): def is_active(self):
return not (self.deleted or self.banned) return not (self.deleted or self.banned)

View file

@ -3,7 +3,7 @@ from django.utils.decorators import method_decorator
from django.views.generic import FormView, RedirectView, TemplateView from django.views.generic import FormView, RedirectView, TemplateView
from users.decorators import admin_required from users.decorators import admin_required
from users.models import Identity, User from users.models import Identity
from users.views.admin.domains import ( # noqa from users.views.admin.domains import ( # noqa
DomainCreate, DomainCreate,
DomainDelete, DomainDelete,
@ -23,6 +23,7 @@ from users.views.admin.settings import ( # noqa
TuningSettings, TuningSettings,
) )
from users.views.admin.stator import Stator # noqa from users.views.admin.stator import Stator # noqa
from users.views.admin.users import UserEdit, UsersRoot # noqa
@method_decorator(admin_required, name="dispatch") @method_decorator(admin_required, name="dispatch")
@ -30,18 +31,6 @@ class AdminRoot(RedirectView):
pattern_name = "admin_basic" pattern_name = "admin_basic"
@method_decorator(admin_required, name="dispatch")
class Users(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") @method_decorator(admin_required, name="dispatch")
class Identities(TemplateView): class Identities(TemplateView):

View file

@ -0,0 +1,84 @@
from django import forms
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, ListView
from users.decorators import admin_required
from users.models import User
@method_decorator(admin_required, name="dispatch")
class UsersRoot(ListView):
template_name = "admin/users.html"
paginate_by = 50
def get(self, request, *args, **kwargs):
self.query = request.GET.get("query")
self.extra_context = {
"section": "users",
"query": self.query or "",
}
return super().get(request, *args, **kwargs)
def get_queryset(self):
users = User.objects.annotate(
num_identities=models.Count("identities")
).order_by("created")
if self.query:
users = users.filter(email__icontains=self.query)
return users
@method_decorator(admin_required, name="dispatch")
class UserEdit(FormView):
template_name = "admin/user_edit.html"
extra_context = {
"section": "users",
}
class form_class(forms.Form):
status = forms.ChoiceField(
choices=[
("normal", "Normal User"),
("moderator", "Moderator"),
("admin", "Admin"),
("banned", "Banned"),
]
)
def dispatch(self, request, id, *args, **kwargs):
self.user = get_object_or_404(User, id=id)
return super().dispatch(request, *args, **kwargs)
def get_initial(self):
status = "normal"
if self.user.moderator:
status = "moderator"
if self.user.admin:
status = "admin"
if self.user.banned:
status = "banned"
return {
"email": self.user.email,
"status": status,
}
def form_valid(self, form):
# Don't let them change themselves
if self.user == self.request.user:
return redirect(".")
status = form.cleaned_data["status"]
self.user.banned = status == "banned"
self.user.moderator = status == "moderator"
self.user.admin = status == "admin"
self.user.save()
return redirect(self.user.urls.admin)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["editing_user"] = self.user
context["same_user"] = self.user == self.request.user
return context