diff --git a/aleksis/core/rules.py b/aleksis/core/rules.py
index e854c3077dd60e2cf7bd22e02dcd0240689876b3..35b00f23770f80e3a5ca821393af3bf0956833f8 100644
--- a/aleksis/core/rules.py
+++ b/aleksis/core/rules.py
@@ -66,6 +66,12 @@ edit_person_predicate = has_person & (
 )
 add_perm("core.edit_person", edit_person_predicate)
 
+# Delete person
+delete_person_predicate = has_person & (
+    has_global_perm("core.delete_person") | has_object_perm("core.delete_person")
+)
+add_perm("core.delete_person", delete_person_predicate)
+
 # Link persons with accounts
 link_persons_accounts_predicate = has_person & has_global_perm("core.link_persons_accounts")
 add_perm("core.link_persons_accounts", link_persons_accounts_predicate)
@@ -88,6 +94,12 @@ edit_group_predicate = has_person & (
 )
 add_perm("core.edit_group", edit_group_predicate)
 
+# Delete group
+delete_group_predicate = has_person & (
+    has_global_perm("core.delete_group") | has_object_perm("core.delete_group")
+)
+add_perm("core.delete_group", delete_group_predicate)
+
 # Assign child groups to groups
 assign_child_groups_to_groups_predicate = has_person & has_global_perm(
     "core.assign_child_groups_to_groups"
@@ -229,6 +241,18 @@ view_group_type_predicate = has_person & (
 )
 add_perm("core.view_grouptype", view_group_type_predicate)
 
+# Create person
+create_person_predicate = has_person & (
+    has_global_perm("core.create_person") | has_object_perm("core.create_person")
+)
+add_perm("core.create_person", create_person_predicate)
+
+# Create group
+create_group_predicate = has_person & (
+    has_global_perm("core.create_group") | has_object_perm("core.create_group")
+)
+add_perm("core.create_group", create_group_predicate)
+
 # School years
 view_school_term_predicate = has_person & has_global_perm("core.view_schoolterm")
 add_perm("core.view_schoolterm", view_school_term_predicate)
diff --git a/aleksis/core/templates/core/group/full.html b/aleksis/core/templates/core/group/full.html
index 2c778f837a785d431300ea57a1a283a05b6bd367..ba5bba0aa1208565331d748d106b4d2e982f7918 100644
--- a/aleksis/core/templates/core/group/full.html
+++ b/aleksis/core/templates/core/group/full.html
@@ -13,8 +13,9 @@
 
   {% has_perm 'core.edit_group' user group as can_change_group %}
   {% has_perm 'core.change_group_preferences' user group as can_change_group_preferences %}
+  {% has_perm 'core.delete_group' user group as can_delete_group %}
 
-  {% if can_change_group or can_change_group_preferences %}
+  {% if can_change_group or can_change_group_preferences or can_delete_group %}
     <p>
       {% if can_change_group %}
         <a href="{% url 'edit_group_by_id' group.id %}" class="btn waves-effect waves-light">
@@ -22,6 +23,14 @@
           {% trans "Edit" %}
         </a>
       {% endif %}
+
+      {% if can_delete_group %}
+        <a href="{% url 'delete_group_by_id' group.id %}" class="btn waves-effect waves-light red">
+          <i class="material-icons left">delete</i>
+          {% trans "Delete" %}
+        </a>
+      {% endif %}
+
       {% if can_change_group_preferences %}
         <a href="{% url "preferences_group" group.id %}" class="btn waves-effect waves-light">
           <i class="material-icons left">settings</i>
diff --git a/aleksis/core/templates/core/person/full.html b/aleksis/core/templates/core/person/full.html
index 68119a4b641ecfa67b14befaf0749069d7dd3e4f..73d2a4cf663999b2648e497e25559e83df69bb24 100644
--- a/aleksis/core/templates/core/person/full.html
+++ b/aleksis/core/templates/core/person/full.html
@@ -12,8 +12,9 @@
 
   {% has_perm 'core.edit_person' user person as can_change_person %}
   {% has_perm 'core.change_person_preferences' user person as can_change_person_preferences %}
+  {% has_perm 'core.delete_person' user person as can_delete_person %}
 
-  {% if can_change_person or can_change_person_preferences %}
+  {% if can_change_person or can_change_person_preferences or can_delete_person %}
     <p>
       {% if can_change_person %}
         <a href="{% url 'edit_person_by_id' person.id %}" class="btn waves-effect waves-light">
@@ -22,6 +23,13 @@
         </a>
       {% endif %}
 
+      {% if can_delete_person %}
+        <a href="{% url 'delete_person_by_id' person.id %}" class="btn waves-effect waves-light red">
+          <i class="material-icons left">delete</i>
+          {% trans "Delete" %}
+        </a>
+      {% endif %}
+
       {% if can_change_person_preferences %}
         <a href="{% url "preferences_person" person.id %}" class="btn waves-effect waves-light">
           <i class="material-icons left">settings</i>
diff --git a/aleksis/core/templates/core/person/list.html b/aleksis/core/templates/core/person/list.html
index dfecfb7c52b64cbdf49e70d06d1148e82128577f..1103199e002bda1394b3028df4483c69804268d8 100644
--- a/aleksis/core/templates/core/person/list.html
+++ b/aleksis/core/templates/core/person/list.html
@@ -2,12 +2,21 @@
 
 {% extends "core/base.html" %}
 
-{% load i18n %}
+{% load i18n rules %}
 {% load render_table from django_tables2 %}
 
 {% block browser_title %}{% blocktrans %}Persons{% endblocktrans %}{% endblock %}
 {% block page_title %}{% blocktrans %}Persons{% endblocktrans %}{% endblock %}
 
 {% block content %}
+  {% has_perm 'core.create_person' user person as can_create_person %}
+
+  {% if can_create_person %}
+    <a class="btn green waves-effect waves-light" href="{% url 'create_person' %}">
+      <i class="material-icons left">add</i>
+      {% trans "Create person" %}
+    </a>
+  {% endif %}
+
   {% render_table persons_table %}
 {% endblock %}
diff --git a/aleksis/core/urls.py b/aleksis/core/urls.py
index 5f08e2d4c036184900e36ea3ef163e66b87edfa1..51048e5c83bdb6c540254a36754f32cdc3059591 100644
--- a/aleksis/core/urls.py
+++ b/aleksis/core/urls.py
@@ -29,8 +29,10 @@ urlpatterns = [
     path("persons", views.persons, name="persons"),
     path("persons/accounts", views.persons_accounts, name="persons_accounts"),
     path("person", views.person, name="person"),
+    path("person/create", views.edit_person, name="create_person"),
     path("person/<int:id_>", views.person, name="person_by_id"),
     path("person/<int:id_>/edit", views.edit_person, name="edit_person_by_id"),
+    path("person/<int:id_>/delete", views.delete_person, name="delete_person_by_id"),
     path("groups", views.groups, name="groups"),
     path("groups/additional_fields", views.additional_fields, name="additional_fields"),
     path("groups/child_groups/", views.groups_child_groups, name="groups_child_groups"),
@@ -52,6 +54,7 @@ urlpatterns = [
     path("group/create", views.edit_group, name="create_group"),
     path("group/<int:id_>", views.group, name="group_by_id"),
     path("group/<int:id_>/edit", views.edit_group, name="edit_group_by_id"),
+    path("group/<int:id_>/delete", views.delete_group, name="delete_group_by_id"),
     path("", views.index, name="index"),
     path(
         "notifications/mark-read/<int:id_>",
diff --git a/aleksis/core/views.py b/aleksis/core/views.py
index 3a94dc46a04b554aaf6a7b4625aa968abe84a2b4..80ddd946c02d7e051f33cbdb5e4f4157c13384c5 100644
--- a/aleksis/core/views.py
+++ b/aleksis/core/views.py
@@ -9,6 +9,7 @@ from django.shortcuts import get_object_or_404, redirect, render
 from django.urls import reverse_lazy
 from django.utils.translation import gettext_lazy as _
 
+import reversion
 from django_tables2 import RequestConfig, SingleTableView
 from dynamic_preferences.forms import preference_form_builder
 from guardian.shortcuts import get_objects_for_user
@@ -274,21 +275,28 @@ def groups_child_groups(request: HttpRequest) -> HttpResponse:
     return render(request, "core/group/child_groups.html", context)
 
 
-@permission_required(
-    "core.edit_person", fn=objectgetter_optional(Person, "request.user.person", True)
-)
+@permission_required("core.edit_person", fn=objectgetter_optional(Person))
 def edit_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse:
     """Edit view for a single person, defaulting to logged-in person."""
     context = {}
 
-    person = objectgetter_optional(Person, "request.user.person", True)(request, id_)
+    person = objectgetter_optional(Person)(request, id_)
     context["person"] = person
 
-    edit_person_form = EditPersonForm(request.POST or None, request.FILES or None, instance=person)
+    if id_:
+        # Edit form for existing group
+        edit_person_form = EditGroupForm(request.POST or None, instance=person)
+    else:
+        # Empty form to create a new group
+        if request.user.has_perm("core.create_person"):
+            edit_person_form = EditPersonForm(request.POST or None)
+        else:
+            raise PermissionDenied()
 
     if request.method == "POST":
         if edit_person_form.is_valid():
-            edit_person_form.save(commit=True)
+            with reversion.create_revision():
+                edit_person_form.save(commit=True)
             messages.success(request, _("The person has been saved."))
 
             # Redirect to self to ensure post-processed data is displayed
@@ -319,11 +327,15 @@ def edit_group(request: HttpRequest, id_: Optional[int] = None) -> HttpResponse:
         edit_group_form = EditGroupForm(request.POST or None, instance=group)
     else:
         # Empty form to create a new group
-        edit_group_form = EditGroupForm(request.POST or None)
+        if request.user.has_perm("core.create_group"):
+            edit_group_form = EditGroupForm(request.POST or None)
+        else:
+            raise PermissionDenied()
 
     if request.method == "POST":
         if edit_group_form.is_valid():
-            group = edit_group_form.save(commit=True)
+            with reversion.create_revision():
+                group = edit_group_form.save(commit=True)
 
             messages.success(request, _("The group has been saved."))
 
@@ -514,6 +526,33 @@ def preferences(
     return render(request, "dynamic_preferences/form.html", context)
 
 
+@permission_required("core.delete_person", fn=objectgetter_optional(Person))
+def delete_person(request: HttpRequest, id_: int) -> HttpResponse:
+    """View to delete an person."""
+    person = objectgetter_optional(Person)(request, id_)
+
+    with reversion.create_revision():
+        person.save()
+
+    person.delete()
+    messages.success(request, _("The person has been deleted."))
+
+    return redirect("persons")
+
+
+@permission_required("core.delete_group", fn=objectgetter_optional(Group))
+def delete_group(request: HttpRequest, id_: int) -> HttpResponse:
+    """View to delete an group."""
+    group = objectgetter_optional(Group)(request, id_)
+    with reversion.create_revision():
+        group.save()
+
+    group.delete()
+    messages.success(request, _("The group has been deleted."))
+
+    return redirect("groups")
+
+
 @permission_required(
     "core.change_additionalfield", fn=objectgetter_optional(AdditionalField, None, False)
 )