punkweb-bb 0.1.4__py3-none-any.whl → 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. punkweb_bb/__pycache__/forms.cpython-311.pyc +0 -0
  2. punkweb_bb/__pycache__/models.cpython-311.pyc +0 -0
  3. punkweb_bb/__pycache__/tests.cpython-311.pyc +0 -0
  4. punkweb_bb/__pycache__/urls.cpython-311.pyc +0 -0
  5. punkweb_bb/__pycache__/utils.cpython-311.pyc +0 -0
  6. punkweb_bb/__pycache__/views.cpython-311.pyc +0 -0
  7. punkweb_bb/forms.py +40 -7
  8. punkweb_bb/models.py +21 -0
  9. punkweb_bb/static/punkweb_bb/css/category-form.css +24 -0
  10. punkweb_bb/static/punkweb_bb/css/index.css +6 -0
  11. punkweb_bb/static/punkweb_bb/css/punkweb.css +61 -5
  12. punkweb_bb/static/punkweb_bb/css/shoutbox.css +13 -2
  13. punkweb_bb/static/punkweb_bb/css/subcategory-form.css +24 -0
  14. punkweb_bb/static/punkweb_bb/css/subcategory.css +12 -0
  15. punkweb_bb/static/punkweb_bb/css/thread.css +19 -0
  16. punkweb_bb/static/punkweb_bb/css/variables.css +1 -0
  17. punkweb_bb/templates/punkweb_bb/category_create.html +50 -0
  18. punkweb_bb/templates/punkweb_bb/category_update.html +53 -0
  19. punkweb_bb/templates/punkweb_bb/index.html +27 -1
  20. punkweb_bb/templates/punkweb_bb/partials/category_delete.html +11 -0
  21. punkweb_bb/templates/punkweb_bb/partials/shout_delete.html +11 -0
  22. punkweb_bb/templates/punkweb_bb/partials/subcategory_delete.html +11 -0
  23. punkweb_bb/templates/punkweb_bb/shoutbox/shout_list.html +14 -4
  24. punkweb_bb/templates/punkweb_bb/subcategory.html +21 -6
  25. punkweb_bb/templates/punkweb_bb/subcategory_create.html +53 -0
  26. punkweb_bb/templates/punkweb_bb/subcategory_update.html +56 -0
  27. punkweb_bb/templates/punkweb_bb/thread.html +26 -6
  28. punkweb_bb/templates/punkweb_bb/thread_create.html +1 -0
  29. punkweb_bb/templatetags/__pycache__/can_delete.cpython-311.pyc +0 -0
  30. punkweb_bb/templatetags/__pycache__/can_edit.cpython-311.pyc +0 -0
  31. punkweb_bb/templatetags/__pycache__/can_post.cpython-311.pyc +0 -0
  32. punkweb_bb/templatetags/can_delete.py +8 -0
  33. punkweb_bb/templatetags/can_edit.py +8 -0
  34. punkweb_bb/templatetags/can_post.py +8 -0
  35. punkweb_bb/tests.py +5 -5
  36. punkweb_bb/urls.py +27 -0
  37. punkweb_bb/utils.py +17 -0
  38. punkweb_bb/views.py +232 -45
  39. {punkweb_bb-0.1.4.dist-info → punkweb_bb-0.2.0.dist-info}/METADATA +2 -2
  40. {punkweb_bb-0.1.4.dist-info → punkweb_bb-0.2.0.dist-info}/RECORD +43 -26
  41. {punkweb_bb-0.1.4.dist-info → punkweb_bb-0.2.0.dist-info}/LICENSE +0 -0
  42. {punkweb_bb-0.1.4.dist-info → punkweb_bb-0.2.0.dist-info}/WHEEL +0 -0
  43. {punkweb_bb-0.1.4.dist-info → punkweb_bb-0.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,56 @@
1
+ {% extends 'punkweb_bb/base.html' %}
2
+ {% load static %}
3
+
4
+ {% block title_prefix %}Edit Subcategory | {% endblock %}
5
+
6
+ {% block extra_head %}
7
+ {{form.media.css}}
8
+ <link rel="stylesheet" href="{% static 'punkweb_bb/css/subcategory-form.css' %}" />
9
+ {% endblock %}
10
+
11
+ {% block content %}
12
+
13
+ <nav>
14
+ <ul class="pw-breadcrumb">
15
+ <li class="pw-breadcrumb-item">
16
+ <a href="{% url 'punkweb_bb:index' %}">Home</a>
17
+ </li>
18
+ <li class="pw-breadcrumb-item">
19
+ <a href="{{subcategory.category.get_absolute_url}}">{{subcategory.category.name}}</a>
20
+ </li>
21
+ <li class="pw-breadcrumb-item">
22
+ <a href="{{subcategory.get_absolute_url}}">{{subcategory.name}}</a>
23
+ </li>
24
+ <li class="pw-breadcrumb-item active">
25
+ Edit
26
+ </li>
27
+ </ul>
28
+ </nav>
29
+
30
+ <div class="pw-card fluid">
31
+ <div class="pw-card-header">Edit Subcategory</div>
32
+ <div class="pw-card-content">
33
+ <form class="subcategoryForm" action="{% url 'punkweb_bb:subcategory_update' subcategory.slug %}" method="post">
34
+ {% csrf_token %}
35
+ {% for field in form %}
36
+ <div class="subcategoryForm__field">
37
+ <label class="pw-input-label" for="{{ field.id_for_label }}">{{ field.label }}</label>
38
+ {{ field }}
39
+ </div>
40
+ {% endfor %}
41
+ {% if form.non_field_errors %}
42
+ {{ form.non_field_errors }}
43
+ {% endif %}
44
+ <div class="subcategoryForm__actions">
45
+ <a class="pw-button default" href="{{subcategory.get_absolute_url}}">Cancel</a>
46
+ <button class="pw-button raised primary" type="submit">Save</button>
47
+ </div>
48
+ </form>
49
+ </div>
50
+ </div>
51
+
52
+ {% endblock %}
53
+
54
+ {% block extra_script %}
55
+ {{form.media.js}}
56
+ {% endblock %}
@@ -1,5 +1,5 @@
1
1
  {% extends 'punkweb_bb/base.html' %}
2
- {% load static %}
2
+ {% load static can_delete can_edit can_post %}
3
3
 
4
4
  {% block title_prefix %}{{thread.title}} | {% endblock %}
5
5
 
@@ -67,6 +67,13 @@
67
67
  <div class="thread__user__info__value">{{thread.user.profile.post_count}}</div>
68
68
  </div>
69
69
  </div>
70
+ {% if thread.user.groups.all|length > 0 %}
71
+ <div class="thread__user__groups">
72
+ {% for group in thread.user.groups.all %}
73
+ <div class="thread__user__group">{{group.name}}</div>
74
+ {% endfor %}
75
+ </div>
76
+ {% endif %}
70
77
  </div>
71
78
  <div class="thread__main">
72
79
  <div class="thread__content">
@@ -77,13 +84,17 @@
77
84
  {{thread.user.profile.signature.rendered}}
78
85
  </div>
79
86
  {% endif %}
80
- {% if thread.user == request.user %}
87
+ {% if thread|can_delete:request.user or thread|can_edit:request.user %}
81
88
  <div class="thread__actions">
89
+ {% if thread|can_edit:request.user %}
82
90
  <a class="pw-button ghost sm" href="{% url 'punkweb_bb:thread_update' thread.id %}">Edit</a>
91
+ {% endif %}
92
+ {% if thread|can_delete:request.user %}
83
93
  <a class="pw-button ghost danger sm" hx-get="{% url 'punkweb_bb:thread_delete' thread.id %}"
84
94
  hx-target="#modal-portal">
85
95
  Delete
86
96
  </a>
97
+ {% endif %}
87
98
  </div>
88
99
  {% endif %}
89
100
  </div>
@@ -128,6 +139,13 @@
128
139
  <div class="thread__user__info__value">{{post.user.profile.post_count}}</div>
129
140
  </div>
130
141
  </div>
142
+ {% if post.user.groups.all|length > 0 %}
143
+ <div class="thread__user__groups">
144
+ {% for group in post.user.groups.all %}
145
+ <div class="thread__user__group">{{group.name}}</div>
146
+ {% endfor %}
147
+ </div>
148
+ {% endif %}
131
149
  </div>
132
150
  <div class="thread__main">
133
151
  <div class="thread__content">
@@ -138,13 +156,17 @@
138
156
  {{post.user.profile.signature.rendered}}
139
157
  </div>
140
158
  {% endif %}
141
- {% if post.user == request.user %}
159
+ {% if post|can_delete:request.user or post|can_edit:request.user %}
142
160
  <div class="thread__actions">
161
+ {% if post|can_edit:request.user %}
143
162
  <a class="pw-button ghost sm" hx-get="{% url 'punkweb_bb:post_update' post.id %}" hx-target="#modal-portal">Edit</a>
163
+ {% endif %}
164
+ {% if post|can_delete:request.user %}
144
165
  <a class="pw-button ghost danger sm" hx-get="{% url 'punkweb_bb:post_delete' post.id %}"
145
166
  hx-target="#modal-portal">
146
167
  Delete
147
168
  </a>
169
+ {% endif %}
148
170
  </div>
149
171
  {% endif %}
150
172
  </div>
@@ -198,8 +220,7 @@
198
220
  </nav>
199
221
  {% endif %}
200
222
 
201
- {% if request.user.is_authenticated %}
202
- {% if not thread.is_closed %}
223
+ {% if thread|can_post:request.user %}
203
224
  <div class="pw-card fluid margin">
204
225
  <div class="pw-card-header">Reply to thread</div>
205
226
  <div class="pw-card-content">
@@ -229,6 +250,5 @@
229
250
  </div>
230
251
  </div>
231
252
  {% endif %}
232
- {% endif %}
233
253
 
234
254
  {% endblock %}
@@ -42,6 +42,7 @@
42
42
  {{ form.non_field_errors }}
43
43
  {% endif %}
44
44
  <div class="threadForm__actions">
45
+ <a class="pw-button default" href="{{subcategory.get_absolute_url}}" type="submit">Cancel</a>
45
46
  <button class="pw-button raised primary" type="submit">Create Thread</button>
46
47
  </div>
47
48
  </form>
@@ -0,0 +1,8 @@
1
+ from django import template
2
+
3
+ register = template.Library()
4
+
5
+
6
+ @register.filter
7
+ def can_delete(obj, user):
8
+ return obj.can_delete(user)
@@ -0,0 +1,8 @@
1
+ from django import template
2
+
3
+ register = template.Library()
4
+
5
+
6
+ @register.filter
7
+ def can_edit(obj, user):
8
+ return obj.can_edit(user)
@@ -0,0 +1,8 @@
1
+ from django import template
2
+
3
+ register = template.Library()
4
+
5
+
6
+ @register.filter
7
+ def can_post(obj, user):
8
+ return obj.can_post(user)
punkweb_bb/tests.py CHANGED
@@ -474,7 +474,7 @@ class ThreadCreateViewTestCase(TestCase):
474
474
  self.client.force_login(self.user)
475
475
  response = self.client.get(self.staff_only_url)
476
476
 
477
- self.assertRedirects(response, self.staff_subcategory.get_absolute_url())
477
+ self.assertEqual(response.status_code, 403)
478
478
 
479
479
 
480
480
  class ThreadViewTestCase(TestCase):
@@ -522,7 +522,7 @@ class ThreadUpdateViewTestCase(TestCase):
522
522
  def test_is_author(self):
523
523
  self.client.force_login(self.other_user)
524
524
  response = self.client.get(self.url)
525
- self.assertEqual(response.status_code, 404)
525
+ self.assertEqual(response.status_code, 403)
526
526
 
527
527
  self.client.force_login(self.user)
528
528
  response = self.client.get(self.url)
@@ -571,7 +571,7 @@ class ThreadDeleteViewTestCase(TestCase):
571
571
  def test_is_author(self):
572
572
  self.client.force_login(self.other_user)
573
573
  response = self.client.get(self.url)
574
- self.assertEqual(response.status_code, 404)
574
+ self.assertEqual(response.status_code, 403)
575
575
 
576
576
  self.client.force_login(self.user)
577
577
  response = self.client.get(self.url)
@@ -653,7 +653,7 @@ class PostUpdateViewTestCase(TestCase):
653
653
  def test_is_author(self):
654
654
  self.client.force_login(self.other_user)
655
655
  response = self.client.get(self.url)
656
- self.assertEqual(response.status_code, 404)
656
+ self.assertEqual(response.status_code, 403)
657
657
 
658
658
  self.client.force_login(self.user)
659
659
  response = self.client.get(self.url)
@@ -703,7 +703,7 @@ class PostDeleteViewTestCase(TestCase):
703
703
  def test_is_author(self):
704
704
  self.client.force_login(self.other_user)
705
705
  response = self.client.get(self.url)
706
- self.assertEqual(response.status_code, 404)
706
+ self.assertEqual(response.status_code, 403)
707
707
 
708
708
  self.client.force_login(self.user)
709
709
  response = self.client.get(self.url)
punkweb_bb/urls.py CHANGED
@@ -11,11 +11,37 @@ urlpatterns = [
11
11
  path("settings/", views.settings_view, name="settings"),
12
12
  path("members/", views.members_view, name="members"),
13
13
  path("members/<path:user_id>/", views.profile_view, name="profile"),
14
+ path("create-category/", views.category_create_view, name="category_create"),
15
+ path(
16
+ "category/<str:category_slug>/update/",
17
+ views.category_update_view,
18
+ name="category_update",
19
+ ),
20
+ path(
21
+ "category/<str:category_slug>/delete/",
22
+ views.category_delete_view,
23
+ name="category_delete",
24
+ ),
14
25
  path(
15
26
  "subcategory/<str:subcategory_slug>/",
16
27
  views.subcategory_view,
17
28
  name="subcategory",
18
29
  ),
30
+ path(
31
+ "category/<str:category_slug>/create-subcategory/",
32
+ views.subcategory_create_view,
33
+ name="subcategory_create",
34
+ ),
35
+ path(
36
+ "subcategory/<str:subcategory_slug>/update/",
37
+ views.subcategory_update_view,
38
+ name="subcategory_update",
39
+ ),
40
+ path(
41
+ "subcategory/<str:subcategory_slug>/delete/",
42
+ views.subcategory_delete_view,
43
+ name="subcategory_delete",
44
+ ),
19
45
  path(
20
46
  "subcategory/<str:subcategory_slug>/create-thread/",
21
47
  views.thread_create_view,
@@ -37,5 +63,6 @@ urlpatterns = [
37
63
  path("post/<str:post_id>/update/", views.post_update_view, name="post_update"),
38
64
  path("shout-list/", views.shout_list_view, name="shout_list"),
39
65
  path("shout-create/", views.shout_create_view, name="shout_create"),
66
+ path("shout/<str:shout_id>/delete/", views.shout_delete_view, name="shout_delete"),
40
67
  path("bbcode/", views.bbcode_view, name="bbcode"),
41
68
  ]
punkweb_bb/utils.py ADDED
@@ -0,0 +1,17 @@
1
+ from django.utils.text import slugify
2
+
3
+
4
+ def get_unique_slug(model, field):
5
+ base_slug = slugify(field)
6
+ slug = base_slug
7
+ unique_slug_exists = True
8
+
9
+ counter = 1
10
+ while unique_slug_exists:
11
+ if model.objects.filter(slug=slug).exists():
12
+ slug = f"{base_slug}-{counter}"
13
+ counter += 1
14
+ else:
15
+ unique_slug_exists = False
16
+
17
+ return slug
punkweb_bb/views.py CHANGED
@@ -4,54 +4,46 @@ from django.contrib.auth import authenticate, get_user_model, login, logout
4
4
  from django.contrib.auth.decorators import login_required
5
5
  from django.http import HttpResponseForbidden
6
6
  from django.shortcuts import get_object_or_404, redirect, render
7
+ from django.urls import reverse
7
8
  from django.utils import timezone
8
9
 
9
- from punkweb_bb.guests import guest_list
10
10
  from punkweb_bb.forms import (
11
11
  BoardProfileModelForm,
12
+ CategoryModelForm,
12
13
  LoginForm,
13
14
  PostModelForm,
14
15
  ShoutModelForm,
15
16
  SignUpForm,
17
+ SubcategoryModelForm,
16
18
  ThreadModelForm,
17
19
  )
20
+ from punkweb_bb.guests import guest_list
18
21
  from punkweb_bb.models import Category, Post, Shout, Subcategory, Thread
19
22
  from punkweb_bb.pagination import paginate_qs
20
23
  from punkweb_bb.response import htmx_redirect
24
+ from punkweb_bb.utils import get_unique_slug
21
25
 
22
26
  User = get_user_model()
23
27
 
24
28
 
25
- def index_view(request):
26
- categories = Category.objects.all()
27
-
28
- recent_threads = Thread.objects.all().order_by("-created_at")[:5]
29
+ def signup_view(request):
30
+ if request.user.is_authenticated:
31
+ return redirect("punkweb_bb:index")
29
32
 
30
- thread_count = Thread.objects.count()
31
- post_count = Post.objects.count()
33
+ if request.method == "POST":
34
+ form = SignUpForm(request.POST)
32
35
 
33
- users = User.objects.select_related("profile").all()
34
- newest_user = users.order_by("-profile__created_at").first()
36
+ if form.is_valid():
37
+ form.save()
35
38
 
36
- users_online = [user for user in users if user.profile.is_online]
37
- members_online = [user for user in users_online if not user.is_staff]
38
- staff_online = [user for user in users_online if user.is_staff]
39
- guests_online = guest_list.length()
40
- total_online = len(members_online) + len(staff_online) + guests_online
39
+ return redirect("punkweb_bb:login")
40
+ else:
41
+ form = SignUpForm()
41
42
 
42
43
  context = {
43
- "categories": categories,
44
- "recent_threads": recent_threads,
45
- "thread_count": thread_count,
46
- "post_count": post_count,
47
- "users": users,
48
- "newest_user": newest_user,
49
- "members_online": members_online,
50
- "staff_online": staff_online,
51
- "guests_online": guests_online,
52
- "total_online": total_online,
44
+ "form": form,
53
45
  }
54
- return render(request, "punkweb_bb/index.html", context=context)
46
+ return render(request, "punkweb_bb/signup.html", context)
55
47
 
56
48
 
57
49
  def login_view(request):
@@ -85,24 +77,36 @@ def logout_view(request):
85
77
  return redirect("punkweb_bb:login")
86
78
 
87
79
 
88
- def signup_view(request):
89
- if request.user.is_authenticated:
90
- return redirect("punkweb_bb:index")
80
+ def index_view(request):
81
+ categories = Category.objects.all()
91
82
 
92
- if request.method == "POST":
93
- form = SignUpForm(request.POST)
83
+ recent_threads = Thread.objects.all().order_by("-created_at")[:5]
94
84
 
95
- if form.is_valid():
96
- form.save()
85
+ thread_count = Thread.objects.count()
86
+ post_count = Post.objects.count()
97
87
 
98
- return redirect("punkweb_bb:login")
99
- else:
100
- form = SignUpForm()
88
+ users = User.objects.select_related("profile").all()
89
+ newest_user = users.order_by("-profile__created_at").first()
90
+
91
+ users_online = [user for user in users if user.profile.is_online]
92
+ members_online = [user for user in users_online if not user.is_staff]
93
+ staff_online = [user for user in users_online if user.is_staff]
94
+ guests_online = guest_list.length()
95
+ total_online = len(members_online) + len(staff_online) + guests_online
101
96
 
102
97
  context = {
103
- "form": form,
98
+ "categories": categories,
99
+ "recent_threads": recent_threads,
100
+ "thread_count": thread_count,
101
+ "post_count": post_count,
102
+ "users": users,
103
+ "newest_user": newest_user,
104
+ "members_online": members_online,
105
+ "staff_online": staff_online,
106
+ "guests_online": guests_online,
107
+ "total_online": total_online,
104
108
  }
105
- return render(request, "punkweb_bb/signup.html", context)
109
+ return render(request, "punkweb_bb/index.html", context=context)
106
110
 
107
111
 
108
112
  def profile_view(request, user_id):
@@ -148,6 +152,72 @@ def settings_view(request):
148
152
  return render(request, "punkweb_bb/settings.html", context=context)
149
153
 
150
154
 
155
+ @login_required(login_url="/login/")
156
+ def category_create_view(request):
157
+ if not request.user.has_perm("punkweb_bb.add_category"):
158
+ return HttpResponseForbidden("You do not have permission to create categories.")
159
+
160
+ if request.method == "POST":
161
+ form = CategoryModelForm(request.POST)
162
+
163
+ if form.is_valid():
164
+ category = form.save(commit=False)
165
+ category.slug = get_unique_slug(Category, category.name)
166
+ category.save()
167
+
168
+ return redirect(category)
169
+ else:
170
+ form = CategoryModelForm()
171
+
172
+ context = {
173
+ "form": form,
174
+ }
175
+ return render(request, "punkweb_bb/category_create.html", context=context)
176
+
177
+
178
+ @login_required(login_url="/login/")
179
+ def category_update_view(request, category_slug):
180
+ if not request.user.has_perm("punkweb_bb.change_category"):
181
+ return HttpResponseForbidden("You do not have permission to change categories.")
182
+
183
+ category = get_object_or_404(Category, slug=category_slug)
184
+
185
+ if request.method == "POST":
186
+ form = CategoryModelForm(request.POST, instance=category)
187
+
188
+ if form.is_valid():
189
+ category = form.save()
190
+
191
+ return redirect(category)
192
+ else:
193
+ form = CategoryModelForm(instance=category)
194
+
195
+ context = {
196
+ "category": category,
197
+ "form": form,
198
+ }
199
+ return render(request, "punkweb_bb/category_update.html", context=context)
200
+
201
+
202
+ @login_required(login_url="/login/")
203
+ def category_delete_view(request, category_slug):
204
+ if not request.user.has_perm("punkweb_bb.delete_category"):
205
+ return HttpResponseForbidden("You do not have permission to delete categories.")
206
+
207
+ category = get_object_or_404(Category, slug=category_slug)
208
+
209
+ if request.method == "DELETE":
210
+ category.delete()
211
+
212
+ return htmx_redirect(reverse("punkweb_bb:index"))
213
+
214
+ context = {
215
+ "category": category,
216
+ }
217
+
218
+ return render(request, "punkweb_bb/partials/category_delete.html", context=context)
219
+
220
+
151
221
  def subcategory_view(request, subcategory_slug):
152
222
  subcategory = get_object_or_404(Subcategory, slug=subcategory_slug)
153
223
 
@@ -160,12 +230,92 @@ def subcategory_view(request, subcategory_slug):
160
230
  return render(request, "punkweb_bb/subcategory.html", context=context)
161
231
 
162
232
 
233
+ @login_required(login_url="/login/")
234
+ def subcategory_create_view(request, category_slug):
235
+ if not request.user.has_perm("punkweb_bb.add_subcategory"):
236
+ return HttpResponseForbidden(
237
+ "You do not have permission to create subcategories."
238
+ )
239
+
240
+ category = get_object_or_404(Category, slug=category_slug)
241
+
242
+ if request.method == "POST":
243
+ form = SubcategoryModelForm(request.POST)
244
+
245
+ if form.is_valid():
246
+ subcategory = form.save(commit=False)
247
+ subcategory.category = category
248
+ subcategory.slug = get_unique_slug(Subcategory, subcategory.name)
249
+ subcategory.save()
250
+
251
+ return redirect(subcategory)
252
+ else:
253
+ form = SubcategoryModelForm()
254
+
255
+ context = {
256
+ "category": category,
257
+ "form": form,
258
+ }
259
+ return render(request, "punkweb_bb/subcategory_create.html", context=context)
260
+
261
+
262
+ @login_required(login_url="/login/")
263
+ def subcategory_update_view(request, subcategory_slug):
264
+ if not request.user.has_perm("punkweb_bb.change_subcategory"):
265
+ return HttpResponseForbidden(
266
+ "You do not have permission to change subcategories."
267
+ )
268
+
269
+ subcategory = get_object_or_404(Subcategory, slug=subcategory_slug)
270
+
271
+ if request.method == "POST":
272
+ form = SubcategoryModelForm(request.POST, instance=subcategory)
273
+
274
+ if form.is_valid():
275
+ subcategory = form.save()
276
+
277
+ return redirect(subcategory)
278
+ else:
279
+ form = SubcategoryModelForm(instance=subcategory)
280
+
281
+ context = {
282
+ "subcategory": subcategory,
283
+ "form": form,
284
+ }
285
+ return render(request, "punkweb_bb/subcategory_update.html", context=context)
286
+
287
+
288
+ @login_required(login_url="/login/")
289
+ def subcategory_delete_view(request, subcategory_slug):
290
+ if not request.user.has_perm("punkweb_bb.delete_subcategory"):
291
+ return HttpResponseForbidden(
292
+ "You do not have permission to delete subcategories."
293
+ )
294
+
295
+ subcategory = get_object_or_404(Subcategory, slug=subcategory_slug)
296
+
297
+ if request.method == "DELETE":
298
+ subcategory.delete()
299
+
300
+ return htmx_redirect(subcategory.category.get_absolute_url())
301
+
302
+ context = {
303
+ "subcategory": subcategory,
304
+ }
305
+
306
+ return render(
307
+ request, "punkweb_bb/partials/subcategory_delete.html", context=context
308
+ )
309
+
310
+
163
311
  @login_required(login_url="/login/")
164
312
  def thread_create_view(request, subcategory_slug):
165
313
  subcategory = get_object_or_404(Subcategory, slug=subcategory_slug)
166
314
 
167
- if subcategory.staff_post_only and not request.user.is_staff:
168
- return redirect(subcategory)
315
+ if not subcategory.can_post(request.user):
316
+ return HttpResponseForbidden(
317
+ "You do not have permission to post in this subcategory."
318
+ )
169
319
 
170
320
  if request.method == "POST":
171
321
  form = ThreadModelForm(request.POST)
@@ -211,7 +361,12 @@ def thread_view(request, thread_id):
211
361
 
212
362
  @login_required(login_url="/login/")
213
363
  def thread_update_view(request, thread_id):
214
- thread = get_object_or_404(Thread, pk=thread_id, user=request.user)
364
+ thread = get_object_or_404(Thread, pk=thread_id)
365
+
366
+ if not thread.can_edit(request.user):
367
+ return HttpResponseForbidden(
368
+ "You do not have permission to change this thread."
369
+ )
215
370
 
216
371
  if request.method == "POST":
217
372
  form = ThreadModelForm(request.POST, instance=thread)
@@ -232,7 +387,12 @@ def thread_update_view(request, thread_id):
232
387
 
233
388
  @login_required(login_url="/login/")
234
389
  def thread_delete_view(request, thread_id):
235
- thread = get_object_or_404(Thread, pk=thread_id, user=request.user)
390
+ thread = get_object_or_404(Thread, pk=thread_id)
391
+
392
+ if not thread.can_delete(request.user):
393
+ return HttpResponseForbidden(
394
+ "You do not have permission to delete this thread."
395
+ )
236
396
 
237
397
  if request.method == "DELETE":
238
398
  thread.delete()
@@ -250,8 +410,10 @@ def thread_delete_view(request, thread_id):
250
410
  def post_create_view(request, thread_id):
251
411
  thread = get_object_or_404(Thread, pk=thread_id)
252
412
 
253
- if thread.is_closed:
254
- return HttpResponseForbidden("Cannot add posts to a closed thread.")
413
+ if not thread.can_post(request.user):
414
+ return HttpResponseForbidden(
415
+ "You do not have permission to post in this thread."
416
+ )
255
417
 
256
418
  form = PostModelForm(request.POST)
257
419
 
@@ -266,7 +428,10 @@ def post_create_view(request, thread_id):
266
428
 
267
429
  @login_required(login_url="/login/")
268
430
  def post_update_view(request, post_id):
269
- post = get_object_or_404(Post, pk=post_id, user=request.user)
431
+ post = get_object_or_404(Post, pk=post_id)
432
+
433
+ if not post.can_edit(request.user):
434
+ return HttpResponseForbidden("You do not have permission to change this post.")
270
435
 
271
436
  if request.method == "POST":
272
437
  form = PostModelForm(request.POST, instance=post)
@@ -288,7 +453,10 @@ def post_update_view(request, post_id):
288
453
 
289
454
  @login_required(login_url="/login/")
290
455
  def post_delete_view(request, post_id):
291
- post = get_object_or_404(Post, pk=post_id, user=request.user)
456
+ post = get_object_or_404(Post, pk=post_id)
457
+
458
+ if not post.can_delete(request.user):
459
+ return HttpResponseForbidden("You do not have permission to delete this post.")
292
460
 
293
461
  if request.method == "DELETE":
294
462
  post.delete()
@@ -340,6 +508,25 @@ def shout_create_view(request):
340
508
  )
341
509
 
342
510
 
511
+ @login_required(login_url="/login/")
512
+ def shout_delete_view(request, shout_id):
513
+ shout = get_object_or_404(Shout, pk=shout_id)
514
+
515
+ if not shout.can_delete(request.user):
516
+ return HttpResponseForbidden("You do not have permission to delete this shout.")
517
+
518
+ if request.method == "DELETE":
519
+ shout.delete()
520
+
521
+ return htmx_redirect(reverse("punkweb_bb:index"))
522
+
523
+ context = {
524
+ "shout": shout,
525
+ }
526
+
527
+ return render(request, "punkweb_bb/partials/shout_delete.html", context=context)
528
+
529
+
343
530
  def bbcode_view(request):
344
531
  codes = (
345
532
  ("Bold", "[b]Bold Text[/b]"),