User admin and LD schema fixes
This commit is contained in:
		
							parent
							
								
									1bcdff79e7
								
							
						
					
					
						commit
						45c6978bc3
					
				@ -659,7 +659,7 @@ class Post(StatorModel):
 | 
			
		||||
        if update or created:
 | 
			
		||||
            post.content = data["content"]
 | 
			
		||||
            post.summary = data.get("summary")
 | 
			
		||||
            post.sensitive = data.get("as:sensitive", False)
 | 
			
		||||
            post.sensitive = data.get("sensitive", False)
 | 
			
		||||
            post.url = data.get("url")
 | 
			
		||||
            post.published = parse_ld_date(data.get("published"))
 | 
			
		||||
            post.edited = parse_ld_date(data.get("updated"))
 | 
			
		||||
@ -670,7 +670,7 @@ class Post(StatorModel):
 | 
			
		||||
                if tag["type"].lower() == "mention":
 | 
			
		||||
                    mention_identity = Identity.by_actor_uri(tag["href"], create=True)
 | 
			
		||||
                    post.mentions.add(mention_identity)
 | 
			
		||||
                elif tag["type"].lower() == "as:hashtag":
 | 
			
		||||
                elif tag["type"].lower() == "hashtag":
 | 
			
		||||
                    post.hashtags.append(tag["name"].lower().lstrip("#"))
 | 
			
		||||
                elif tag["type"].lower() == "http://joinmastodon.org/ns#emoji":
 | 
			
		||||
                    emoji = Emoji.by_ap_tag(post.author.domain, tag, create=True)
 | 
			
		||||
 | 
			
		||||
@ -415,6 +415,7 @@ def canonicalise(json_data: dict, include_security: bool = False) -> dict:
 | 
			
		||||
            "votersCount": "toot:votersCount",
 | 
			
		||||
            "Hashtag": "as:Hashtag",
 | 
			
		||||
            "Public": "as:Public",
 | 
			
		||||
            "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
 | 
			
		||||
        },
 | 
			
		||||
    ]
 | 
			
		||||
    if include_security:
 | 
			
		||||
 | 
			
		||||
@ -96,9 +96,14 @@ urlpatterns = [
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "admin/users/",
 | 
			
		||||
        admin.Users.as_view(),
 | 
			
		||||
        admin.UsersRoot.as_view(),
 | 
			
		||||
        name="admin_users",
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "admin/users/<id>/",
 | 
			
		||||
        admin.UserEdit.as_view(),
 | 
			
		||||
        name="admin_user_edit",
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "admin/identities/",
 | 
			
		||||
        admin.Identities.as_view(),
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										36
									
								
								templates/admin/user_edit.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								templates/admin/user_edit.html
									
									
									
									
									
										Normal 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 %}
 | 
			
		||||
@ -3,7 +3,40 @@
 | 
			
		||||
{% block subtitle %}Users{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <p>
 | 
			
		||||
        Please use the <a href="/djadmin/users/user/">Django Admin</a> for now.
 | 
			
		||||
    </p>
 | 
			
		||||
    <form action="." class="search">
 | 
			
		||||
        <input type="search" name="query" value="{{ query }}" placeholder="Search by email">
 | 
			
		||||
        <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 %}
 | 
			
		||||
 | 
			
		||||
@ -155,7 +155,7 @@ def test_fetch_actor(httpx_mock, config_system):
 | 
			
		||||
                "mediaType": "image/jpeg",
 | 
			
		||||
                "url": "https://example.com/image.jpg",
 | 
			
		||||
            },
 | 
			
		||||
            "as:manuallyApprovesFollowers": False,
 | 
			
		||||
            "manuallyApprovesFollowers": False,
 | 
			
		||||
            "name": "Test User",
 | 
			
		||||
            "preferredUsername": "test",
 | 
			
		||||
            "published": "2022-11-02T00:00:00Z",
 | 
			
		||||
 | 
			
		||||
@ -89,7 +89,13 @@ class PasswordResetAdmin(admin.ModelAdmin):
 | 
			
		||||
 | 
			
		||||
@admin.register(InboxMessage)
 | 
			
		||||
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",)
 | 
			
		||||
    search_fields = ["message"]
 | 
			
		||||
    actions = ["reset_state"]
 | 
			
		||||
 | 
			
		||||
@ -438,7 +438,7 @@ class Identity(StatorModel):
 | 
			
		||||
            self.username = self.username["@value"]
 | 
			
		||||
        if self.username:
 | 
			
		||||
            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_id = document.get("publicKey", {}).get("id")
 | 
			
		||||
        self.icon_uri = document.get("icon", {}).get("url")
 | 
			
		||||
 | 
			
		||||
@ -115,6 +115,13 @@ class InboxMessage(StatorModel):
 | 
			
		||||
    def message_object_type(self):
 | 
			
		||||
        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
 | 
			
		||||
    def message_actor(self):
 | 
			
		||||
        return self.message.get("actor")
 | 
			
		||||
 | 
			
		||||
@ -48,7 +48,7 @@ class SystemActor:
 | 
			
		||||
            },
 | 
			
		||||
            "preferredUsername": self.username,
 | 
			
		||||
            "url": self.profile_uri,
 | 
			
		||||
            "as:manuallyApprovesFollowers": True,
 | 
			
		||||
            "manuallyApprovesFollowers": True,
 | 
			
		||||
            "publicKey": {
 | 
			
		||||
                "id": self.public_key_id,
 | 
			
		||||
                "owner": self.actor_uri,
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
import urlman
 | 
			
		||||
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
 | 
			
		||||
from django.db import models
 | 
			
		||||
 | 
			
		||||
@ -44,6 +45,10 @@ class User(AbstractBaseUser):
 | 
			
		||||
 | 
			
		||||
    objects = UserManager()
 | 
			
		||||
 | 
			
		||||
    class urls(urlman.Urls):
 | 
			
		||||
        admin = "/admin/users/"
 | 
			
		||||
        admin_edit = "{admin}{self.pk}/"
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def is_active(self):
 | 
			
		||||
        return not (self.deleted or self.banned)
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@ from django.utils.decorators import method_decorator
 | 
			
		||||
from django.views.generic import FormView, RedirectView, TemplateView
 | 
			
		||||
 | 
			
		||||
from users.decorators import admin_required
 | 
			
		||||
from users.models import Identity, User
 | 
			
		||||
from users.models import Identity
 | 
			
		||||
from users.views.admin.domains import (  # noqa
 | 
			
		||||
    DomainCreate,
 | 
			
		||||
    DomainDelete,
 | 
			
		||||
@ -23,6 +23,7 @@ from users.views.admin.settings import (  # noqa
 | 
			
		||||
    TuningSettings,
 | 
			
		||||
)
 | 
			
		||||
from users.views.admin.stator import Stator  # noqa
 | 
			
		||||
from users.views.admin.users import UserEdit, UsersRoot  # noqa
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@method_decorator(admin_required, name="dispatch")
 | 
			
		||||
@ -30,18 +31,6 @@ class AdminRoot(RedirectView):
 | 
			
		||||
    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")
 | 
			
		||||
class Identities(TemplateView):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										84
									
								
								users/views/admin/users.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								users/views/admin/users.py
									
									
									
									
									
										Normal 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
 | 
			
		||||
		Reference in New Issue
	
	Block a user