punkweb-bb 0.1.2__py3-none-any.whl → 0.1.3__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.
@@ -0,0 +1,18 @@
1
+ # Generated by Django 4.2.11 on 2024-05-07 04:24
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ("punkweb_bb", "0001_initial"),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AddField(
14
+ model_name="thread",
15
+ name="view_count",
16
+ field=models.PositiveIntegerField(default=0),
17
+ ),
18
+ ]
punkweb_bb/models.py CHANGED
@@ -5,6 +5,7 @@ import os
5
5
  from django.contrib.auth import get_user_model
6
6
  from django.core.cache import cache
7
7
  from django.db import models
8
+ from django.forms import ValidationError
8
9
  from django.urls import reverse
9
10
  from django.utils import timezone
10
11
  from precise_bbcode.fields import BBCodeTextField
@@ -107,6 +108,7 @@ class Thread(UUIDPrimaryKeyMixin, TimestampMixin):
107
108
  is_pinned = models.BooleanField(default=False)
108
109
  is_closed = models.BooleanField(default=False)
109
110
  last_post_created_at = models.DateTimeField(auto_now_add=True)
111
+ view_count = models.PositiveIntegerField(default=0)
110
112
 
111
113
  class Meta:
112
114
  ordering = (
@@ -161,6 +163,8 @@ class Post(UUIDPrimaryKeyMixin, TimestampMixin):
161
163
  return thread_url
162
164
 
163
165
  def save(self, *args, **kwargs):
166
+ if self.thread.is_closed:
167
+ raise ValidationError("Cannot add posts to a closed thread.")
164
168
  if self._state.adding:
165
169
  self.thread.last_post_created_at = timezone.now()
166
170
  self.thread.save()
punkweb_bb/settings.py CHANGED
@@ -4,4 +4,5 @@ PUNKWEB_BB = getattr(settings, "PUNKWEB_BB", {})
4
4
 
5
5
  SITE_NAME = PUNKWEB_BB.get("SITE_NAME", "PUNKWEB")
6
6
  SITE_TITLE = PUNKWEB_BB.get("SITE_TITLE", "PunkwebBB")
7
+ FAVICON = PUNKWEB_BB.get("FAVICON", "punkweb_bb/favicon.ico")
7
8
  SHOUTBOX_ENABLED = PUNKWEB_BB.get("SHOUTBOX_ENABLED", True)
@@ -53,3 +53,10 @@
53
53
  .profile__thread:not(:last-of-type) {
54
54
  border-bottom: 1px solid var(--border);
55
55
  }
56
+
57
+ @media screen and (max-width: 768px) {
58
+ .profile__info {
59
+ align-items: start;
60
+ flex-direction: column;
61
+ }
62
+ }
@@ -6,7 +6,7 @@
6
6
  <meta charset="UTF-8" />
7
7
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
8
  <title>{% block title_prefix %}{% endblock %}{{ punkweb_bb.settings.SITE_TITLE|default:"PunkwebBB" }}</title>
9
- <link rel="icon" href="{% static 'punkweb_bb/favicon.ico' %}" />
9
+ <link rel="icon" href="{% static punkweb_bb.settings.FAVICON %}" />
10
10
  <link rel="stylesheet" href="{% static 'punkweb_bb/vendor/open-color.css' %}" />
11
11
  <link rel="stylesheet" href="{% static 'punkweb_bb/vendor/prism.css' %}" />
12
12
  <link rel="stylesheet" href="{% static 'punkweb_bb/css/defaults.css' %}" />
@@ -1,5 +1,5 @@
1
1
  {% extends 'punkweb_bb/base.html' %}
2
- {% load static %}
2
+ {% load static humanize_int %}
3
3
 
4
4
  {% block title_prefix %}{{subcategory.name}} | {% endblock%}
5
5
 
@@ -14,6 +14,9 @@
14
14
  <li class="pw-breadcrumb-item">
15
15
  <a href="{% url 'punkweb_bb:index' %}">Home</a>
16
16
  </li>
17
+ <li class="pw-breadcrumb-item">
18
+ <a href="{{subcategory.category.get_absolute_url}}">{{subcategory.category.name}}</a>
19
+ </li>
17
20
  <li class="pw-breadcrumb-item active">
18
21
  {{subcategory.name}}
19
22
  </li>
@@ -44,12 +47,14 @@
44
47
  <colgroup>
45
48
  <col span="1">
46
49
  <col span="1" width="96px">
50
+ <col span="1" width="96px">
47
51
  <col span="1" width="160px">
48
52
  </colgroup>
49
53
  <thead>
50
54
  <tr>
51
55
  <th>Title</th>
52
56
  <th>Posts</th>
57
+ <th>Views</th>
53
58
  <th></th>
54
59
  </tr>
55
60
  </thead>
@@ -74,6 +79,7 @@
74
79
  {{thread.created_at|date:'M j, Y'}}</div>
75
80
  </td>
76
81
  <td>{{thread.post_count}}</td>
82
+ <td>{{thread.view_count | humanize_int}}</td>
77
83
  <td>
78
84
  {% if thread.latest_post %}
79
85
  <div class="thread__latestPost">
@@ -21,6 +21,9 @@
21
21
  <li class="pw-breadcrumb-item">
22
22
  <a href="{% url 'punkweb_bb:index' %}">Home</a>
23
23
  </li>
24
+ <li class="pw-breadcrumb-item">
25
+ <a href="{{thread.subcategory.category.get_absolute_url}}">{{thread.subcategory.category.name}}</a>
26
+ </li>
24
27
  <li class="pw-breadcrumb-item">
25
28
  <a href="{{thread.subcategory.get_absolute_url}}">{{thread.subcategory.name}}</a>
26
29
  </li>
@@ -56,7 +59,7 @@
56
59
  <div class="thread__user__info__row">
57
60
  <div class="thread__user__info__label">Joined:</div>
58
61
  <div class="thread__user__info__value">
59
- {{thread.user.profile.created_at|date:"M d, Y"}}
62
+ {{thread.user.date_joined|date:"M d, Y"}}
60
63
  </div>
61
64
  </div>
62
65
  <div class="thread__user__info__row">
@@ -117,7 +120,7 @@
117
120
  <div class="thread__user__info__row">
118
121
  <div class="thread__user__info__label">Joined:</div>
119
122
  <div class="thread__user__info__value">
120
- {{post.user.profile.created_at|date:"M d, Y"}}
123
+ {{post.user.date_joined|date:"M d, Y"}}
121
124
  </div>
122
125
  </div>
123
126
  <div class="thread__user__info__row">
@@ -15,6 +15,9 @@
15
15
  <li class="pw-breadcrumb-item">
16
16
  <a href="{% url 'punkweb_bb:index' %}">Home</a>
17
17
  </li>
18
+ <li class="pw-breadcrumb-item">
19
+ <a href="{{subcategory.category.get_absolute_url}}">{{subcategory.category.name}}</a>
20
+ </li>
18
21
  <li class="pw-breadcrumb-item">
19
22
  <a href="{{subcategory.get_absolute_url}}">{{subcategory.name}}</a>
20
23
  </li>
@@ -15,6 +15,9 @@
15
15
  <li class="pw-breadcrumb-item">
16
16
  <a href="{% url 'punkweb_bb:index' %}">Home</a>
17
17
  </li>
18
+ <li class="pw-breadcrumb-item">
19
+ <a href="{{thread.subcategory.category.get_absolute_url}}">{{thread.subcategory.category.name}}</a>
20
+ </li>
18
21
  <li class="pw-breadcrumb-item">
19
22
  <a href="{{thread.subcategory.get_absolute_url}}">{{thread.subcategory.name}}</a>
20
23
  </li>
@@ -0,0 +1,12 @@
1
+ from django import template
2
+
3
+ register = template.Library()
4
+
5
+
6
+ @register.filter
7
+ def humanize_int(value):
8
+ if value >= 1000000:
9
+ return f"{value / 1000000:.1f}M"
10
+ elif value >= 1000:
11
+ return f"{value / 1000:.1f}K"
12
+ return value
punkweb_bb/tests.py CHANGED
@@ -2,7 +2,9 @@ import math
2
2
 
3
3
  from django.contrib.auth import get_user_model
4
4
  from django.core.cache import cache
5
- from django.test import TestCase
5
+ from django.forms import ValidationError
6
+ from django.test import Client, TestCase
7
+ from django.urls import reverse
6
8
  from django.utils import timezone
7
9
 
8
10
  from punkweb_bb.models import (
@@ -141,7 +143,7 @@ class ThreadTestCase(TestCase):
141
143
  self.user = User.objects.create_user(username="test", password="test")
142
144
  self.category = Category.objects.create(name="test")
143
145
  self.subcategory = Subcategory.objects.create(
144
- name="test", category=self.category
146
+ name="test", category=self.category, slug="test"
145
147
  )
146
148
 
147
149
  def test_thread_str(self):
@@ -175,6 +177,64 @@ class ThreadTestCase(TestCase):
175
177
 
176
178
  self.assertEqual(thread.latest_post, post_2)
177
179
 
180
+ def test_last_post_created_at(self):
181
+ thread = Thread.objects.create(
182
+ subcategory=self.subcategory, user=self.user, title="test", content="test"
183
+ )
184
+ initial_last_post_created_at = thread.last_post_created_at
185
+ Post.objects.create(thread=thread, user=self.user, content="test")
186
+ new_last_post_created_at = thread.last_post_created_at
187
+
188
+ self.assertGreater(new_last_post_created_at, initial_last_post_created_at)
189
+
190
+ def test_is_closed(self):
191
+ thread = Thread.objects.create(
192
+ subcategory=self.subcategory,
193
+ user=self.user,
194
+ title="test",
195
+ content="test",
196
+ is_closed=True,
197
+ )
198
+ self.assertEqual(thread.is_closed, True)
199
+
200
+ post = Post(
201
+ thread=thread,
202
+ user=self.user,
203
+ content="test",
204
+ )
205
+
206
+ self.assertRaises(ValidationError, post.save)
207
+
208
+ def test_is_pinned(self):
209
+ thread = Thread.objects.create(
210
+ subcategory=self.subcategory,
211
+ user=self.user,
212
+ title="test1",
213
+ content="test1",
214
+ is_pinned=True,
215
+ )
216
+ self.assertEqual(thread.is_pinned, True)
217
+
218
+ Thread.objects.create(
219
+ subcategory=self.subcategory, user=self.user, title="test2", content="test2"
220
+ )
221
+
222
+ threads = Thread.objects.all()
223
+
224
+ self.assertEqual(threads[0], thread)
225
+
226
+ def test_bump(self):
227
+ thread1 = Thread.objects.create(
228
+ subcategory=self.subcategory, user=self.user, title="test1", content="test1"
229
+ )
230
+ Thread.objects.create(
231
+ subcategory=self.subcategory, user=self.user, title="test2", content="test2"
232
+ )
233
+ Post.objects.create(thread=thread1, user=self.user, content="test1")
234
+ threads = Thread.objects.all()
235
+
236
+ self.assertEqual(threads[0], thread1)
237
+
178
238
  def test_get_absolute_url(self):
179
239
  thread = Thread.objects.create(
180
240
  subcategory=self.subcategory, user=self.user, title="test", content="test"
@@ -228,3 +288,459 @@ class ShoutTestCase(TestCase):
228
288
  def test_str(self):
229
289
  shout = Shout.objects.create(user=self.user, content="test")
230
290
  self.assertEqual(str(shout), f"{shout.user} > {shout.created_at}")
291
+
292
+
293
+ class IndexViewTestCase(TestCase):
294
+ def setUp(self):
295
+ self.client = Client()
296
+ self.url = reverse("punkweb_bb:index")
297
+ self.user = User.objects.create(username="test", password="test")
298
+ self.staff_user = User.objects.create(
299
+ username="staff", password="staff", is_staff=True
300
+ )
301
+
302
+ def test_users_online(self):
303
+ response = self.client.get(self.url)
304
+ self.assertEqual(len(response.context["users_online"]), 0)
305
+
306
+ self.client.force_login(self.user)
307
+ response = self.client.get(self.url)
308
+
309
+ self.assertEqual(len(response.context["users_online"]), 1)
310
+
311
+ def test_staff_online(self):
312
+ response = self.client.get(self.url)
313
+ self.assertEqual(len(response.context["staff_online"]), 0)
314
+
315
+ self.client.force_login(self.staff_user)
316
+ response = self.client.get(self.url)
317
+
318
+ self.assertEqual(len(response.context["staff_online"]), 1)
319
+
320
+ def test_newest_user(self):
321
+ response = self.client.get(self.url)
322
+ self.assertEqual(response.context["newest_user"], self.staff_user)
323
+
324
+ def test_users_count(self):
325
+ response = self.client.get(self.url)
326
+ self.assertEqual(response.context["users"].count(), 2)
327
+
328
+
329
+ class LoginViewTestCase(TestCase):
330
+ def setUp(self):
331
+ self.client = Client()
332
+ self.url = reverse("punkweb_bb:login")
333
+ self.user = User.objects.create_user(username="test", password="test")
334
+
335
+ def test_redirect_authenticated_user(self):
336
+ self.client.force_login(self.user)
337
+ response = self.client.get(self.url)
338
+
339
+ self.assertRedirects(response, reverse("punkweb_bb:index"))
340
+
341
+ def test_login(self):
342
+ response = self.client.post(
343
+ self.url, {"username": "test", "password": "test"}, follow=True
344
+ )
345
+
346
+ self.assertRedirects(response, reverse("punkweb_bb:index"))
347
+ self.assertTrue(response.context["user"].is_authenticated)
348
+
349
+
350
+ class LogoutViewTestCase(TestCase):
351
+ def setUp(self):
352
+ self.client = Client()
353
+ self.url = reverse("punkweb_bb:logout")
354
+ self.user = User.objects.create_user(username="test", password="test")
355
+
356
+ def test_logout(self):
357
+ self.client.force_login(self.user)
358
+ response = self.client.get(self.url, follow=True)
359
+
360
+ self.assertRedirects(response, reverse("punkweb_bb:login"))
361
+ self.assertFalse(response.context["user"].is_authenticated)
362
+
363
+
364
+ class SignupViewTestCase(TestCase):
365
+ def setUp(self):
366
+ self.client = Client()
367
+ self.url = reverse("punkweb_bb:signup")
368
+ self.user = User.objects.create_user(username="test1", password="test")
369
+
370
+ def test_redirect_authenticated_user(self):
371
+ self.client.force_login(self.user)
372
+ response = self.client.get(self.url)
373
+
374
+ self.assertRedirects(response, reverse("punkweb_bb:index"))
375
+
376
+ def test_signup(self):
377
+ response = self.client.post(
378
+ self.url,
379
+ {
380
+ "username": "test2",
381
+ "password1": "needsmorecomplexity",
382
+ "password2": "needsmorecomplexity",
383
+ },
384
+ )
385
+
386
+ self.assertRedirects(response, reverse("punkweb_bb:login"))
387
+
388
+
389
+ class SettingsViewTestCase(TestCase):
390
+ def setUp(self):
391
+ self.client = Client()
392
+ self.url = reverse("punkweb_bb:settings")
393
+ self.user = User.objects.create_user(username="test", password="test")
394
+
395
+ def test_redirect_unauthenticated_user(self):
396
+ response = self.client.get(self.url)
397
+
398
+ self.assertRedirects(response, f"{reverse('punkweb_bb:login')}?next={self.url}")
399
+
400
+ def test_settings(self):
401
+ self.client.force_login(self.user)
402
+ response = self.client.get(self.url)
403
+
404
+ self.assertEqual(response.status_code, 200)
405
+
406
+ self.client.post(
407
+ self.url,
408
+ {
409
+ "signature": "[b]test[/b]",
410
+ },
411
+ )
412
+
413
+ self.user.profile.refresh_from_db()
414
+ self.assertEqual(self.user.profile._signature_rendered, "<strong>test</strong>")
415
+
416
+ self.assertEqual(response.status_code, 200)
417
+
418
+
419
+ class ThreadCreateViewTestCase(TestCase):
420
+ def setUp(self):
421
+ self.client = Client()
422
+ self.user = User.objects.create_user(username="test", password="test")
423
+ self.category = Category.objects.create(name="test")
424
+ self.subcategory = Subcategory.objects.create(
425
+ name="test", category=self.category, slug="test"
426
+ )
427
+ self.staff_subcategory = Subcategory.objects.create(
428
+ name="staff", category=self.category, slug="staff", staff_post_only=True
429
+ )
430
+ self.url = reverse("punkweb_bb:thread_create", args=[self.subcategory.slug])
431
+ self.staff_only_url = reverse(
432
+ "punkweb_bb:thread_create", args=[self.staff_subcategory.slug]
433
+ )
434
+
435
+ def test_redirect_unauthenticated_user(self):
436
+ response = self.client.get(self.url)
437
+
438
+ self.assertRedirects(response, f"{reverse('punkweb_bb:login')}?next={self.url}")
439
+
440
+ def test_thread_create(self):
441
+ self.client.force_login(self.user)
442
+ response = self.client.get(self.url)
443
+
444
+ self.assertEqual(response.status_code, 200)
445
+
446
+ response = self.client.post(
447
+ self.url,
448
+ {
449
+ "title": "test",
450
+ "content": "test",
451
+ },
452
+ follow=True,
453
+ )
454
+
455
+ new_thread = Thread.objects.first()
456
+
457
+ self.assertRedirects(response, new_thread.get_absolute_url())
458
+ self.assertEqual(Thread.objects.count(), 1)
459
+ self.assertEqual(new_thread.user, self.user)
460
+ self.assertEqual(new_thread.subcategory, self.subcategory)
461
+
462
+ def test_thread_create_staff_post_only(self):
463
+ self.client.force_login(self.user)
464
+ response = self.client.get(self.staff_only_url)
465
+
466
+ self.assertRedirects(response, self.staff_subcategory.get_absolute_url())
467
+
468
+
469
+ class ThreadViewTestCase(TestCase):
470
+ def setUp(self):
471
+ self.client = Client()
472
+ self.user = User.objects.create_user(username="test", password="test")
473
+ self.category = Category.objects.create(name="test")
474
+ self.subcategory = Subcategory.objects.create(
475
+ name="test", category=self.category, slug="test"
476
+ )
477
+ self.thread = Thread.objects.create(
478
+ subcategory=self.subcategory, user=self.user, title="test", content="test"
479
+ )
480
+ self.url = reverse("punkweb_bb:thread", args=[self.thread.id])
481
+
482
+ def test_view_count(self):
483
+ response = self.client.get(self.url)
484
+ self.assertEqual(response.context["thread"].view_count, 1)
485
+
486
+ # ensure that the view count does not increase when viewed again from the same session
487
+
488
+ response = self.client.get(self.url)
489
+ self.assertEqual(response.context["thread"].view_count, 1)
490
+
491
+
492
+ class ThreadUpdateViewTestCase(TestCase):
493
+ def setUp(self):
494
+ self.client = Client()
495
+ self.user = User.objects.create_user(username="test", password="test")
496
+ self.other_user = User.objects.create_user(username="other", password="other")
497
+ self.category = Category.objects.create(name="test")
498
+ self.subcategory = Subcategory.objects.create(
499
+ name="test", category=self.category, slug="test"
500
+ )
501
+ self.thread = Thread.objects.create(
502
+ subcategory=self.subcategory, user=self.user, title="test", content="test"
503
+ )
504
+ self.url = reverse("punkweb_bb:thread_update", args=[self.thread.id])
505
+
506
+ def test_redirect_unauthenticated_user(self):
507
+ response = self.client.get(self.url)
508
+
509
+ self.assertRedirects(response, f"{reverse('punkweb_bb:login')}?next={self.url}")
510
+
511
+ def test_is_author(self):
512
+ self.client.force_login(self.other_user)
513
+ response = self.client.get(self.url)
514
+ self.assertEqual(response.status_code, 404)
515
+
516
+ self.client.force_login(self.user)
517
+ response = self.client.get(self.url)
518
+ self.assertEqual(response.status_code, 200)
519
+
520
+ def test_thread_update(self):
521
+ self.client.force_login(self.user)
522
+ response = self.client.get(self.url)
523
+
524
+ self.assertEqual(response.status_code, 200)
525
+
526
+ response = self.client.post(
527
+ self.url,
528
+ {
529
+ "title": "edit",
530
+ "content": "edit",
531
+ },
532
+ follow=True,
533
+ )
534
+
535
+ self.assertRedirects(response, self.thread.get_absolute_url())
536
+ self.thread.refresh_from_db()
537
+ self.assertEqual(self.thread.title, "edit")
538
+ self.assertEqual(self.thread._content_rendered, "edit")
539
+
540
+
541
+ class ThreadDeleteViewTestCase(TestCase):
542
+ def setUp(self):
543
+ self.client = Client()
544
+ self.user = User.objects.create_user(username="test", password="test")
545
+ self.other_user = User.objects.create_user(username="other", password="other")
546
+ self.category = Category.objects.create(name="test")
547
+ self.subcategory = Subcategory.objects.create(
548
+ name="test", category=self.category, slug="test"
549
+ )
550
+ self.thread = Thread.objects.create(
551
+ subcategory=self.subcategory, user=self.user, title="test", content="test"
552
+ )
553
+ self.url = reverse("punkweb_bb:thread_delete", args=[self.thread.id])
554
+
555
+ def test_redirect_unauthenticated_user(self):
556
+ response = self.client.get(self.url)
557
+
558
+ self.assertRedirects(response, f"{reverse('punkweb_bb:login')}?next={self.url}")
559
+
560
+ def test_is_author(self):
561
+ self.client.force_login(self.other_user)
562
+ response = self.client.get(self.url)
563
+ self.assertEqual(response.status_code, 404)
564
+
565
+ self.client.force_login(self.user)
566
+ response = self.client.get(self.url)
567
+ self.assertEqual(response.status_code, 200)
568
+
569
+ def test_thread_delete(self):
570
+ self.client.force_login(self.user)
571
+ response = self.client.get(self.url)
572
+
573
+ self.assertEqual(response.status_code, 200)
574
+
575
+ response = self.client.delete(self.url, follow=True)
576
+
577
+ self.assertEqual(
578
+ response.headers["HX-Redirect"], self.subcategory.get_absolute_url()
579
+ )
580
+ self.assertEqual(Thread.objects.count(), 0)
581
+
582
+
583
+ class PostCreateViewTestCase(TestCase):
584
+ def setUp(self):
585
+ self.client = Client()
586
+ self.user = User.objects.create_user(username="test", password="test")
587
+ self.category = Category.objects.create(name="test")
588
+ self.subcategory = Subcategory.objects.create(
589
+ name="test", category=self.category, slug="test"
590
+ )
591
+ self.thread = Thread.objects.create(
592
+ subcategory=self.subcategory, user=self.user, title="test", content="test"
593
+ )
594
+ self.url = reverse("punkweb_bb:post_create", args=[self.thread.id])
595
+
596
+ def test_redirect_unauthenticated_user(self):
597
+ response = self.client.get(self.url)
598
+
599
+ self.assertRedirects(response, f"{reverse('punkweb_bb:login')}?next={self.url}")
600
+
601
+ def test_post_create(self):
602
+ self.client.force_login(self.user)
603
+
604
+ response = self.client.post(
605
+ self.url,
606
+ {
607
+ "content": "test",
608
+ },
609
+ follow=True,
610
+ )
611
+
612
+ new_post = Post.objects.first()
613
+
614
+ self.assertRedirects(response, new_post.get_absolute_url())
615
+ self.assertEqual(Post.objects.count(), 1)
616
+ self.assertEqual(new_post.user, self.user)
617
+ self.assertEqual(new_post.thread, self.thread)
618
+
619
+
620
+ class PostUpdateViewTestCase(TestCase):
621
+ def setUp(self):
622
+ self.client = Client()
623
+ self.user = User.objects.create_user(username="test", password="test")
624
+ self.other_user = User.objects.create_user(username="other", password="other")
625
+ self.category = Category.objects.create(name="test")
626
+ self.subcategory = Subcategory.objects.create(
627
+ name="test", category=self.category, slug="test"
628
+ )
629
+ self.thread = Thread.objects.create(
630
+ subcategory=self.subcategory, user=self.user, title="test", content="test"
631
+ )
632
+ self.post = Post.objects.create(
633
+ thread=self.thread, user=self.user, content="test"
634
+ )
635
+ self.url = reverse("punkweb_bb:post_update", args=[self.post.id])
636
+
637
+ def test_redirect_unauthenticated_user(self):
638
+ response = self.client.get(self.url)
639
+
640
+ self.assertRedirects(response, f"{reverse('punkweb_bb:login')}?next={self.url}")
641
+
642
+ def test_is_author(self):
643
+ self.client.force_login(self.other_user)
644
+ response = self.client.get(self.url)
645
+ self.assertEqual(response.status_code, 404)
646
+
647
+ self.client.force_login(self.user)
648
+ response = self.client.get(self.url)
649
+ self.assertEqual(response.status_code, 200)
650
+
651
+ def test_post_update(self):
652
+ self.client.force_login(self.user)
653
+ response = self.client.get(self.url)
654
+
655
+ self.assertEqual(response.status_code, 200)
656
+
657
+ response = self.client.post(
658
+ self.url,
659
+ {
660
+ "content": "edit",
661
+ },
662
+ follow=True,
663
+ )
664
+
665
+ self.assertRedirects(response, self.post.get_absolute_url())
666
+ self.post.refresh_from_db()
667
+ self.assertEqual(self.post._content_rendered, "edit")
668
+
669
+
670
+ class PostDeleteViewTestCase(TestCase):
671
+ def setUp(self):
672
+ self.client = Client()
673
+ self.user = User.objects.create_user(username="test", password="test")
674
+ self.other_user = User.objects.create_user(username="other", password="other")
675
+ self.category = Category.objects.create(name="test")
676
+ self.subcategory = Subcategory.objects.create(
677
+ name="test", category=self.category, slug="test"
678
+ )
679
+ self.thread = Thread.objects.create(
680
+ subcategory=self.subcategory, user=self.user, title="test", content="test"
681
+ )
682
+ self.post = Post.objects.create(
683
+ thread=self.thread, user=self.user, content="test"
684
+ )
685
+ self.url = reverse("punkweb_bb:post_delete", args=[self.post.id])
686
+
687
+ def test_redirect_unauthenticated_user(self):
688
+ response = self.client.get(self.url)
689
+
690
+ self.assertRedirects(response, f"{reverse('punkweb_bb:login')}?next={self.url}")
691
+
692
+ def test_is_author(self):
693
+ self.client.force_login(self.other_user)
694
+ response = self.client.get(self.url)
695
+ self.assertEqual(response.status_code, 404)
696
+
697
+ self.client.force_login(self.user)
698
+ response = self.client.get(self.url)
699
+ self.assertEqual(response.status_code, 200)
700
+
701
+ def test_post_delete(self):
702
+ self.client.force_login(self.user)
703
+ response = self.client.get(self.url)
704
+
705
+ self.assertEqual(response.status_code, 200)
706
+
707
+ response = self.client.delete(self.url, follow=True)
708
+
709
+ self.assertEqual(
710
+ response.headers["HX-Redirect"], self.thread.get_absolute_url()
711
+ )
712
+ self.assertEqual(Post.objects.count(), 0)
713
+
714
+
715
+ class ShoutCreateViewTestCase(TestCase):
716
+ def setUp(self):
717
+ self.client = Client()
718
+ self.user = User.objects.create_user(username="test", password="test")
719
+ self.url = reverse("punkweb_bb:shout_create")
720
+
721
+ def test_unauthenticated(self):
722
+ response = self.client.get(self.url)
723
+
724
+ self.client.post(
725
+ self.url,
726
+ {
727
+ "content": "test",
728
+ },
729
+ )
730
+
731
+ self.assertEqual(Shout.objects.count(), 0)
732
+ self.assertEqual(response.status_code, 200)
733
+
734
+ def test_shout_create(self):
735
+ self.client.force_login(self.user)
736
+
737
+ response = self.client.post(
738
+ self.url,
739
+ {
740
+ "content": "test",
741
+ },
742
+ follow=True,
743
+ )
744
+
745
+ self.assertEqual(Shout.objects.count(), 1)
746
+ self.assertEqual(response.status_code, 200)
punkweb_bb/views.py CHANGED
@@ -108,7 +108,9 @@ def profile_view(request, user_id):
108
108
 
109
109
 
110
110
  def members_view(request):
111
- users = paginate_qs(request, User.objects.select_related("profile").all())
111
+ users = paginate_qs(
112
+ request, User.objects.select_related("profile").order_by("username")
113
+ )
112
114
 
113
115
  context = {
114
116
  "users": users,
@@ -185,6 +187,13 @@ def thread_view(request, thread_id):
185
187
 
186
188
  post_form = PostModelForm()
187
189
 
190
+ # Increment view count if this session hasn't viewed the thread before
191
+ viewed_threads = request.session.get("viewed_threads", [])
192
+ if thread_id not in viewed_threads:
193
+ thread.view_count += 1
194
+ thread.save()
195
+ request.session["viewed_threads"] = viewed_threads + [thread_id]
196
+
188
197
  context = {
189
198
  "thread": thread,
190
199
  "posts": posts,
@@ -235,7 +244,7 @@ def post_create_view(request, thread_id):
235
244
  thread = get_object_or_404(Thread, pk=thread_id)
236
245
 
237
246
  if thread.is_closed:
238
- return HttpResponseForbidden("This thread is closed.")
247
+ return HttpResponseForbidden("Cannot add posts to a closed thread.")
239
248
 
240
249
  form = PostModelForm(request.POST)
241
250
 
@@ -336,7 +345,7 @@ def bbcode_view(request):
336
345
  ("Url", "[url=https://google.com]Link Text[/url]"),
337
346
  (
338
347
  "Image",
339
- "[img]https://punkweb.net/media/music/artists/system-lynx_EklOGpj.png.200x200_q85_crop.png[/img]",
348
+ "[img]https://placehold.co/400[/img]",
340
349
  ),
341
350
  # Custom
342
351
  ("Horizontal Rule", "[hr]"),
@@ -0,0 +1,171 @@
1
+ Metadata-Version: 2.1
2
+ Name: punkweb-bb
3
+ Version: 0.1.3
4
+ Summary: Django application that provides a simple and modern forum board software for your Django website.
5
+ Home-page: https://github.com/Punkweb/PunkwebBB
6
+ Author: Punkweb
7
+ Author-email: punkwebnet@gmail.com
8
+ License: BSD-3-Clause
9
+ Classifier: Environment :: Web Environment
10
+ Classifier: Framework :: Django
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: BSD License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python
15
+ Classifier: Programming Language :: Python :: 3
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: django >=4.0
19
+ Requires-Dist: django-precise-bbcode
20
+ Requires-Dist: pillow
21
+
22
+ # PunkwebBB
23
+
24
+ PunkwebBB is a Django application that provides a simple and modern forum board software for your Django website.
25
+
26
+ This is the successor to [punkweb-boards](https://github.com/Punkweb/punkweb-boards).
27
+
28
+ Check out [punkweb.net](https://punkweb.net/board/) for a live demo and more information!
29
+
30
+ ## Built with
31
+
32
+ - [Django](https://www.djangoproject.com/)
33
+ - [django-precise-bbcode](https://github.com/ellmetha/django-precise-bbcode)
34
+ - [HTMX](https://htmx.org/)
35
+ - [jQuery](https://jquery.com/)
36
+ - [SCEditor](https://www.sceditor.com/)
37
+ - [PrismJS](https://prismjs.com/)
38
+
39
+ ## Requirements
40
+
41
+ - Python 3.11+
42
+ - Django 4.2+
43
+ - django-precise-bbcode 1.2+
44
+ - Pillow
45
+
46
+ It may work with older versions of Python and Django, but it has not been tested.
47
+
48
+ ## Installation
49
+
50
+ ```bash
51
+ pip install punkweb-bb
52
+ ```
53
+
54
+ Add `precise_bbcode` and `punkweb_bb` to your `INSTALLED_APPS` in your Django settings module:
55
+
56
+ ```python
57
+ INSTALLED_APPS = [
58
+ ...
59
+ "precise_bbcode",
60
+ "punkweb_bb",
61
+ ]
62
+ ```
63
+
64
+ _Note_: `precise_bbcode` is required. It must be installed before `punkweb_bb`.
65
+
66
+ Add the following middleware to your `MIDDLEWARE` setting:
67
+
68
+ ```python
69
+ MIDDLEWARE = [
70
+ ...
71
+ "punkweb_bb.middleware.ProfileOnlineCacheMiddleware",
72
+ ]
73
+ ```
74
+
75
+ Add the following context processor to your `TEMPLATES` setting:
76
+
77
+ ```python
78
+ TEMPLATES = [
79
+ {
80
+ ...
81
+ "OPTIONS": {
82
+ "context_processors": [
83
+ ...
84
+ "punkweb_bb.context_processors.punkweb_bb",
85
+ ],
86
+ },
87
+ },
88
+ ]
89
+ ```
90
+
91
+ Add the following URL pattern to your `urls.py`:
92
+
93
+ ```python
94
+ from django.urls import path, include
95
+
96
+ urlpatterns = [
97
+ ...
98
+ path("forum/", include("punkweb_bb.urls")), # or any other path you want
99
+ ]
100
+ ```
101
+
102
+ And finally, install the models:
103
+
104
+ ```bash
105
+ python manage.py migrate
106
+ ```
107
+
108
+ ## Configuration
109
+
110
+ These are the default settings for PunkwebBB, which can be overridden in your Django settings module:
111
+
112
+ ```python
113
+ PUNKWEB_BB = {
114
+ "SITE_NAME": "PUNKWEB",
115
+ "SITE_TITLE": "PunkwebBB",
116
+ "FAVICON": "punkweb_bb/favicon.ico",
117
+ "SHOUTBOX_ENABLED": True,
118
+ }
119
+ ```
120
+
121
+ ## Testing
122
+
123
+ Report:
124
+
125
+ ```bash
126
+ coverage run && coverage report
127
+ ```
128
+
129
+ HTML:
130
+
131
+ ```bash
132
+ coverage run && coverage html
133
+ ```
134
+
135
+ ```bash
136
+ Found 57 test(s).
137
+ Creating test database for alias 'default'...
138
+ System check identified no issues (0 silenced).
139
+ .........................................................
140
+ ----------------------------------------------------------------------
141
+ Ran 57 tests in 8.824s
142
+
143
+ OK
144
+ Destroying test database for alias 'default'...
145
+ Name Stmts Miss Cover
146
+ ----------------------------------------------------------------
147
+ punkweb_bb/__init__.py 0 0 100%
148
+ punkweb_bb/admin.py 41 1 98%
149
+ punkweb_bb/admin_forms.py 28 0 100%
150
+ punkweb_bb/apps.py 6 0 100%
151
+ punkweb_bb/bbcode_tags.py 115 3 97%
152
+ punkweb_bb/context_processors.py 3 0 100%
153
+ punkweb_bb/forms.py 35 0 100%
154
+ punkweb_bb/middleware.py 10 0 100%
155
+ punkweb_bb/mixins.py 11 0 100%
156
+ punkweb_bb/models.py 124 0 100%
157
+ punkweb_bb/pagination.py 11 4 64%
158
+ punkweb_bb/parsers.py 50 2 96%
159
+ punkweb_bb/response.py 3 0 100%
160
+ punkweb_bb/settings.py 6 0 100%
161
+ punkweb_bb/signals.py 9 0 100%
162
+ punkweb_bb/templatetags/__init__.py 0 0 100%
163
+ punkweb_bb/templatetags/humanize_int.py 9 5 44%
164
+ punkweb_bb/templatetags/shoutbox_bbcode.py 9 0 100%
165
+ punkweb_bb/tests.py 410 0 100%
166
+ punkweb_bb/urls.py 4 0 100%
167
+ punkweb_bb/views.py 174 17 90%
168
+ punkweb_bb/widgets.py 8 0 100%
169
+ ----------------------------------------------------------------
170
+ TOTAL 1066 32 97%
171
+ ```
@@ -7,15 +7,15 @@ punkweb_bb/context_processors.py,sha256=BEOGvWVukvxJUxWZBIc32ioB-mGqImSEBuhhZjqI
7
7
  punkweb_bb/forms.py,sha256=O1lzmgumdnSEjBKi10c7ODftIqRmvtEIdNj1IODCikk,1977
8
8
  punkweb_bb/middleware.py,sha256=lF1w7XM07s1kcrOWodzErHop9zpIE5SEE52YLMAbDKg,456
9
9
  punkweb_bb/mixins.py,sha256=XfiThPL7rB71IfukS1ikvYQhfg8RwgSVgsm10Ul1ezM,395
10
- punkweb_bb/models.py,sha256=DZ6DrnRVGbb8Zb5LL9Q2571YDg-0X2_LoBsRhg2J7sg,5314
10
+ punkweb_bb/models.py,sha256=Y5w80EQ85Mut71xiDVMALqLXdydlTxOiZEljmnH5g-E,5519
11
11
  punkweb_bb/pagination.py,sha256=OgoZuJsq9MKMvBKYylJVPaNtM9ni3K8OAvOdi-eGr3M,409
12
12
  punkweb_bb/parsers.py,sha256=VjWSPqpVfypHLHP0NrfLqXB-1b0W6oFuGIzoAiPi71I,2732
13
13
  punkweb_bb/response.py,sha256=dETGVC9Xrsq02pQzmIIWbSUt472lJ4fgLwBKrXnP3t4,130
14
- punkweb_bb/settings.py,sha256=kC0jwMkKNtmpBopSagYUe_lVKu-EEzLORr63aouuXGI,250
14
+ punkweb_bb/settings.py,sha256=rYKMQxbC6rfzOQgcjAoBuMEgKHOITvOX1PrKTweEYKk,312
15
15
  punkweb_bb/signals.py,sha256=bVdfg942Mwq-fYDZ1Z52Q0V2BCk1lgzGz8JVZFPnzJ8,365
16
- punkweb_bb/tests.py,sha256=uCYeK5EQ2bCudge0gOUrDWdBcsx3fYFv6iSgdyjtL5k,8257
16
+ punkweb_bb/tests.py,sha256=BXkvqtr1JbE2WQYkgjse3wp9ky3cwOzQcVHoqNhHJmw,25989
17
17
  punkweb_bb/urls.py,sha256=_mRAtaZoKzPZ_uuOCFy6_lOwFCavv26p5kMKD9MH4c4,1538
18
- punkweb_bb/views.py,sha256=dievICp0o9U-NCJDguW959e5RFf3NDEBfL97aYD-PYw,10435
18
+ punkweb_bb/views.py,sha256=HDJCM8PgDkQu96UbZLFyjSD2K9WIjLhWEO-QBM3APmk,10723
19
19
  punkweb_bb/widgets.py,sha256=eF6CB5nnh_6XJadpDzDKgd9incd0VIR63Rnzdr8T2n0,840
20
20
  punkweb_bb/__pycache__/__init__.cpython-311.pyc,sha256=3PyxCxoznfadaGt0a7re4j0Ky9z9hblufpcwPB85kK8,161
21
21
  punkweb_bb/__pycache__/admin.cpython-311.pyc,sha256=rAPYqRy7hAVZn3qST3ypYdulvH9IBhzWEgvbbAMIMIY,3679
@@ -26,18 +26,21 @@ punkweb_bb/__pycache__/context_processors.cpython-311.pyc,sha256=P7rEsodXonYexbS
26
26
  punkweb_bb/__pycache__/forms.cpython-311.pyc,sha256=JRbtAaXyXgG9eDdBjKBkSn8qeZtdxRc7L2_ceE-MpW8,4680
27
27
  punkweb_bb/__pycache__/middleware.cpython-311.pyc,sha256=gZN0oVrPh9nIUUni_DvPrhMOYPR98SXrYxgHIsUXX-0,1249
28
28
  punkweb_bb/__pycache__/mixins.cpython-311.pyc,sha256=eP1NjqDNYMYXrC45DNkrTqVAUv1vsGBrqPy5U5CB_aw,1478
29
- punkweb_bb/__pycache__/models.cpython-311.pyc,sha256=PrebadNmOX3Ra3tRXhxU26E284LAHJhERXvgyTXXtow,12034
29
+ punkweb_bb/__pycache__/models.cpython-311.pyc,sha256=9W03p0j5_dYnQ4YffZUrN_A5-Bvkbpvfmpr5rOkzyAQ,12308
30
30
  punkweb_bb/__pycache__/pagination.cpython-311.pyc,sha256=r54xmtiRp5dm1n2Xa7oElMFFaYFY0RzmMuF3Er4UqEA,990
31
31
  punkweb_bb/__pycache__/parsers.cpython-311.pyc,sha256=pp8JZt9V3HhquQMGrhglwLc53GxgNFWjjSnK3Bpxf8o,5335
32
32
  punkweb_bb/__pycache__/response.cpython-311.pyc,sha256=CEPckYWZkOrdU2Vow-ni0ZrWEUBww0uJzyr_wv---mM,448
33
- punkweb_bb/__pycache__/settings.cpython-311.pyc,sha256=lGhRUWwPQlXSfwFB5FlMvydr14_IaB7Gd2CXuLLVwrI,563
33
+ punkweb_bb/__pycache__/settings.cpython-311.pyc,sha256=ZjE994_MuSW_9Dn_ufsUH-vrld-BvqEwoCF7YL9tbrA,665
34
34
  punkweb_bb/__pycache__/signals.cpython-311.pyc,sha256=AHChn7hDdrOmCAwKIKKuFOY-4hyBP9uwTkyb5bnFV-Q,864
35
+ punkweb_bb/__pycache__/tests.cpython-311.pyc,sha256=3CM8FL8P1YXVcgShhXqXjg3Yo3T1amKXlVtHRUZ2d7o,50877
35
36
  punkweb_bb/__pycache__/urls.cpython-311.pyc,sha256=80TvNUws6ip1FCts6bubO3DGQJ2yOlB-WReK-qaTnGY,2304
36
- punkweb_bb/__pycache__/views.cpython-311.pyc,sha256=7myiqdsURN68OLr94FjW_75Xxt9G85yX80qn52hMDS4,15313
37
+ punkweb_bb/__pycache__/views.cpython-311.pyc,sha256=bjqhbTnN0-pvIygCzh0NXY6lNJUjMpz8B51EIlpay94,15592
37
38
  punkweb_bb/__pycache__/widgets.cpython-311.pyc,sha256=-RcQ3JapLHw8Bbi4FP05kQJJIa7bnliKPaFpkDCOWvQ,1552
38
39
  punkweb_bb/migrations/0001_initial.py,sha256=3RGsylygBcWx1kIPhSzOb9v_2yvowsxKfxuSinKKuS0,8950
40
+ punkweb_bb/migrations/0002_thread_view_count.py,sha256=JJZT53Mp8Ofht3oIi67s-0wzCdpYyu8wOeCi_B8q8Yo,388
39
41
  punkweb_bb/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
42
  punkweb_bb/migrations/__pycache__/0001_initial.cpython-311.pyc,sha256=koi_VflndmTKzSXWstWBXuVEZgEeueId5NE4kOAlA4Q,6456
43
+ punkweb_bb/migrations/__pycache__/0002_thread_view_count.cpython-311.pyc,sha256=9HRllkD8LHPXadyurYvp2UcmRFEywalUVQjiOGWizgc,815
41
44
  punkweb_bb/migrations/__pycache__/__init__.cpython-311.pyc,sha256=sTbC1AXnh0V4BJwjcjs1ckdeYjG01I348hZwLE2HI4Y,172
42
45
  punkweb_bb/static/punkweb_bb/favicon.ico,sha256=lEfX--R9wEGPkkXgLYCsGmHuAGajigiqBXAoonxq8ZM,318
43
46
  punkweb_bb/static/punkweb_bb/css/defaults.css,sha256=EsYORpHIQ8gotAdiGvBBU38i6F0mICj-OKr-JF6yYVg,1455
@@ -45,7 +48,7 @@ punkweb_bb/static/punkweb_bb/css/index.css,sha256=NqpF7zNMzSrDeHw5gfbwcmeBET4TcS
45
48
  punkweb_bb/static/punkweb_bb/css/login.css,sha256=pt5ul4ycZsVB-No3c5gsQa1zVS1iAZgteN1CcllS26k,234
46
49
  punkweb_bb/static/punkweb_bb/css/members.css,sha256=1Fz0uVDbs3RnuXNjOtnGnK2jok3LEQBoPhjRYp7gNwE,395
47
50
  punkweb_bb/static/punkweb_bb/css/post-form.css,sha256=rEiYplAobLjjUYAcnjNqIjyIVhe9O5hAlPJ3STW-K14,321
48
- punkweb_bb/static/punkweb_bb/css/profile.css,sha256=2guoiUHk8LbCZbZtzp4OrWTbMFfsiiNPzXeI4FPv3MY,799
51
+ punkweb_bb/static/punkweb_bb/css/profile.css,sha256=yfNJT_D-05deqiBrdIgPeCSO_DFSL8-fGEEHnGrCIYM,916
49
52
  punkweb_bb/static/punkweb_bb/css/punkweb-modal.css,sha256=ctwo5bgbAW-k0uqrufDbqeQfAh87-BZ-5gPm8aJHeT4,1296
50
53
  punkweb_bb/static/punkweb_bb/css/punkweb.css,sha256=UZT592v_krB8DPFf6YN1zlKmmFiCAXdS_KqO98GrKNM,11284
51
54
  punkweb_bb/static/punkweb_bb/css/settings.css,sha256=ulQQFTu8slk2rYOmhvci5v-AVnguUuDhKQDhyQOsQNU,308
@@ -185,7 +188,7 @@ punkweb_bb/static/punkweb_bb/vendor/sceditor-3.2.0/minified/themes/office-toolba
185
188
  punkweb_bb/static/punkweb_bb/vendor/sceditor-3.2.0/minified/themes/office.min.css,sha256=EZeNIT-LMxAnrW_7M6BXuH0B8m3MoIS68tDyTxmCoP0,13148
186
189
  punkweb_bb/static/punkweb_bb/vendor/sceditor-3.2.0/minified/themes/square.min.css,sha256=vrNHEnpQJr3o8AlJ2aEhn4fsRqR4TOopE3N3-4oE2ho,10710
187
190
  punkweb_bb/static/punkweb_bb/vendor/sceditor-3.2.0/minified/themes/content/default.min.css,sha256=2jMxGiqcrAhfOtNMdqmTUtfgM6oUz5F0VJ0sUzam9CY,1016
188
- punkweb_bb/templates/punkweb_bb/base.html,sha256=Hk1yL0tPRBpNWZ2A3WmItmwh3kxtVjaLM95jrklxizk,4034
191
+ punkweb_bb/templates/punkweb_bb/base.html,sha256=Gu2ZVeP0p7GnsfXFxByXRd9LT4DNXx0O1czSGjwYg0w,4037
189
192
  punkweb_bb/templates/punkweb_bb/base_modal.html,sha256=OCbtsMWeNCO0Tl1PmHCcGkwoi1OZjeIK_VhNTzMor7M,460
190
193
  punkweb_bb/templates/punkweb_bb/bbcode.html,sha256=1EGBejsOMZOPi6P39oR6E35VdqnfR6wYWeKDl4Xr_js,396
191
194
  punkweb_bb/templates/punkweb_bb/index.html,sha256=2y8gS6UzoJkVXvmw5PEuuxyYIarlNjGzTmmNMuNWNJ8,8127
@@ -194,21 +197,24 @@ punkweb_bb/templates/punkweb_bb/members.html,sha256=ceRCRX0AN7V8d7paz9495y8aQByM
194
197
  punkweb_bb/templates/punkweb_bb/profile.html,sha256=MW1JWLs_lLpg62DDcvcZJRwrHquYA5nCwQqL1v9ZGCo,2948
195
198
  punkweb_bb/templates/punkweb_bb/settings.html,sha256=CmmGdNddZj6Atpofcny0bwEHwTPRPC8vXXThCgsNH90,1994
196
199
  punkweb_bb/templates/punkweb_bb/signup.html,sha256=HP5owUfV2uEuvOccPoBa21XbjBHo0ulV6tjfgdToNLU,1167
197
- punkweb_bb/templates/punkweb_bb/subcategory.html,sha256=S5DCrYYYdU3DbTpsyscbvCDFYSaDIkRqu2Mxy8NIQpo,4682
198
- punkweb_bb/templates/punkweb_bb/thread.html,sha256=CcHK1XfuYxUGyFBP19_45xeeHc3JNqGDvj1Vj4Yp4u0,7457
199
- punkweb_bb/templates/punkweb_bb/thread_create.html,sha256=HcjiJvP2keytgwFrONtOkS2yGaOT1o4i5fCJgndocpc,1399
200
- punkweb_bb/templates/punkweb_bb/thread_update.html,sha256=HzTGHCzV-v1B743vC-dQ72eg--UBrpPbqSVP_aN_9oc,1578
200
+ punkweb_bb/templates/punkweb_bb/subcategory.html,sha256=rLJtBKz2Zd07Lyan6fH2fn7i8bXyfVj9hpnBh6m3PS4,4950
201
+ punkweb_bb/templates/punkweb_bb/thread.html,sha256=igyEa1Qz-joktKmUbgHtIY0EnxlWUCBHDdEXNTuKJDk,7595
202
+ punkweb_bb/templates/punkweb_bb/thread_create.html,sha256=rLIO-ghdzHCiKJ6LCj2l4cxe9QbsMUG13ZxogYF9PUE,1537
203
+ punkweb_bb/templates/punkweb_bb/thread_update.html,sha256=SLL_5tceZ8ZiPbWCO9eOe_aeMgV5lQ-p6eun1_XvKwE,1730
201
204
  punkweb_bb/templates/punkweb_bb/partials/post_delete.html,sha256=tQtQGxTF0tZA5eXqfIuO6NctsS83eEhR4ImmBLbkAXM,417
202
205
  punkweb_bb/templates/punkweb_bb/partials/post_update.html,sha256=mFidDqgTuv4LffuSa8pShXxWmiramLLMhHjsaJs1Y9k,565
203
206
  punkweb_bb/templates/punkweb_bb/partials/thread_delete.html,sha256=m91u_r8qTvMqR2s4VxxFHQvZt-WGgHnHzJL_1KZkX9M,425
204
207
  punkweb_bb/templates/punkweb_bb/shoutbox/shout_list.html,sha256=uAls--UuLgS6NPW2qEsNbutR_aoJ_AuhZ_Fkt-c8ipU,312
205
208
  punkweb_bb/templates/punkweb_bb/shoutbox/shoutbox.html,sha256=J_Lp6KKcqSJr-IayyLN-p0JgMfuwbFP77g-UtcM53WI,672
206
209
  punkweb_bb/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
210
+ punkweb_bb/templatetags/humanize_int.py,sha256=C4KmDG0Jv6o8rwT1RXLdWoGLddJxMxgOoRV9I2598AM,248
207
211
  punkweb_bb/templatetags/shoutbox_bbcode.py,sha256=OH-FsRTyPWZldaFypSVzPLlTrSm4XEOqQW9hBI0ROBk,310
208
- punkweb_bb/templatetags/__pycache__/__init__.cpython-311.pyc,sha256=hieh5xV9m_aA6xLQJ86ABc9pnKF0MnAxwvly-pwYSDo,174
212
+ punkweb_bb/templatetags/__pycache__/__init__.cpython-311.pyc,sha256=VEPKwaIhqpKpSSJiotDYngAUdidDzR9PpPiHtKEl1jA,174
213
+ punkweb_bb/templatetags/__pycache__/humanize_count.cpython-311.pyc,sha256=UKD6_5RX8YpYpg-LPrgGxBkW56THsbYY51cKTYdKwRY,621
214
+ punkweb_bb/templatetags/__pycache__/humanize_int.cpython-311.pyc,sha256=csY5ek-bevYVeM91hYFKozuKWXCTXb7M-7Bokwdxhrk,619
209
215
  punkweb_bb/templatetags/__pycache__/shoutbox_bbcode.cpython-311.pyc,sha256=Jhg9qW-nQe6IDr45rE0ZgeDYF4E61S7kYAYpbMo5ZQ8,833
210
- punkweb_bb-0.1.2.dist-info/LICENSE,sha256=YYysF07B-kyXSO7IWFB9f49ZXa6LIFUTVsR1Ogmhp8s,1507
211
- punkweb_bb-0.1.2.dist-info/METADATA,sha256=ckMonMRB6vfDo-XWU_tetw_v8fiwQTwZxeGrxQ1Uuug,1797
212
- punkweb_bb-0.1.2.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
213
- punkweb_bb-0.1.2.dist-info/top_level.txt,sha256=sWuGdGnk0ejOXiFDzlBqrNs2VbPEx0_i8UwWXn4SuHU,11
214
- punkweb_bb-0.1.2.dist-info/RECORD,,
216
+ punkweb_bb-0.1.3.dist-info/LICENSE,sha256=YYysF07B-kyXSO7IWFB9f49ZXa6LIFUTVsR1Ogmhp8s,1507
217
+ punkweb_bb-0.1.3.dist-info/METADATA,sha256=thU9kjReWNpjs73xcTkXgnO0LKX_-g_wELf87WiE-0Q,4930
218
+ punkweb_bb-0.1.3.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
219
+ punkweb_bb-0.1.3.dist-info/top_level.txt,sha256=sWuGdGnk0ejOXiFDzlBqrNs2VbPEx0_i8UwWXn4SuHU,11
220
+ punkweb_bb-0.1.3.dist-info/RECORD,,
@@ -1,72 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: punkweb-bb
3
- Version: 0.1.2
4
- Summary: Django application that provides a simple and modern forum board software for your Django website.
5
- Home-page: https://github.com/Punkweb/PunkwebBB
6
- Author: Punkweb
7
- Author-email: punkwebnet@gmail.com
8
- License: BSD-3-Clause
9
- Classifier: Environment :: Web Environment
10
- Classifier: Framework :: Django
11
- Classifier: Intended Audience :: Developers
12
- Classifier: License :: OSI Approved :: BSD License
13
- Classifier: Operating System :: OS Independent
14
- Classifier: Programming Language :: Python
15
- Classifier: Programming Language :: Python :: 3
16
- Description-Content-Type: text/markdown
17
- License-File: LICENSE
18
- Requires-Dist: django >=4.0
19
- Requires-Dist: django-precise-bbcode
20
- Requires-Dist: pillow
21
-
22
- # PunkwebBB
23
-
24
- PunkwebBB is a Django application that provides a simple and modern forum board software for your Django website.
25
-
26
- This is the successor to [punkweb-boards](https://github.com/Punkweb/punkweb-boards)
27
-
28
- ## Built with
29
-
30
- - [Django](https://www.djangoproject.com/)
31
- - [django-precise-bbcode](https://github.com/ellmetha/django-precise-bbcode)
32
- - [HTMX](https://htmx.org/)
33
- - [jQuery](https://jquery.com/)
34
- - [SCEditor](https://www.sceditor.com/)
35
- - [PrismJS](https://prismjs.com/)
36
-
37
- ## Requirements
38
-
39
- - Python 3.11+
40
- - Django 4.2+
41
- - django-precise-bbcode 1.2+
42
- - Pillow
43
-
44
- It may work with older versions of Python and Django, but it has not been tested.
45
-
46
- ## Testing
47
-
48
- Report:
49
-
50
- ```bash
51
- coverage run && coverage report
52
- ```
53
-
54
- HTML:
55
-
56
- ```bash
57
- coverage run && coverage html
58
- ```
59
-
60
- ## Preview
61
-
62
- #### Models
63
-
64
- ![Models](https://github.com/Punkweb/PunkwebBB/blob/main/images/models.png)
65
-
66
- #### Index View
67
-
68
- ![Index](https://github.com/Punkweb/PunkwebBB/blob/main/images/index.png)
69
-
70
- #### Thread View
71
-
72
- ![Thread](https://github.com/Punkweb/PunkwebBB/blob/main/images/thread.png)