diff --git a/apps/blog/choices.py b/apps/blog/choices.py new file mode 100644 index 0000000..d008ee8 --- /dev/null +++ b/apps/blog/choices.py @@ -0,0 +1,6 @@ +from django.db.models import TextChoices + + +class StatusChoice(TextChoices): + DRAFT = 'df', 'Draft' + PUBLISHED = 'pb', 'Published' diff --git a/apps/blog/forms.py b/apps/blog/forms.py index 338c1fd..0a472e4 100644 --- a/apps/blog/forms.py +++ b/apps/blog/forms.py @@ -11,56 +11,40 @@ } -class PostCreateForm(forms.ModelForm): +class PostCreateUpdateForm(forms.ModelForm): class Meta: model = Post - fields = ("title", "content", "is_active") + fields = ("title", "description", "content", "status") widgets = { "title": forms.TextInput( attrs={ "class": "form-control", + "name": "title", "placeholder": "Enter you`r post title...", } ), - "content": forms.Textarea( + "description": forms.Textarea( attrs={ "class": "form-control", - "placeholder": "Enter you`r post content...", + "name": "description", + "placeholder": "Enter you`r post description...", } ), - "is_active": forms.CheckboxInput( - attrs={"name": "is_active", "class": "form-check-input"} - ), - } - - -class PostUpdateForm(forms.ModelForm): - - class Meta: - model = Post - fields = ("title", "content", "is_active") - - widgets = { - "title": forms.TextInput( - attrs={ - "name": "title", - "class": "form-control", - "placeholder": "Title....", - }, - ), "content": forms.Textarea( attrs={ - "name": "content", - "cols": "40", - "rows": "10", "class": "form-control", - "placeholder": "Content....", - }, + "name": "content", + "placeholder": "Enter you`r post content...", + } ), - "is_active": forms.CheckboxInput( - attrs={"name": "is_active", "class": "form-check-input"} + "status": forms.Select( + attrs={ + "class": "form-select", + "name": "status", + "placeholder": "Enter you`r post status...", + } ), } diff --git a/apps/blog/managers.py b/apps/blog/managers.py new file mode 100644 index 0000000..eb1878c --- /dev/null +++ b/apps/blog/managers.py @@ -0,0 +1,9 @@ +from django.db import models +from .choices import StatusChoice + + +class PublishedManager(models.Manager): + def get_queryset(self) -> models.QuerySet: + return ( + super().get_queryset().filter(status=StatusChoice.PUBLISHED, is_active=True) + ) diff --git a/apps/blog/migrations/0002_post_description_post_status.py b/apps/blog/migrations/0002_post_description_post_status.py new file mode 100644 index 0000000..e607ee2 --- /dev/null +++ b/apps/blog/migrations/0002_post_description_post_status.py @@ -0,0 +1,24 @@ +# Generated by Django 5.1.1 on 2024-11-13 09:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('blog', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='post', + name='description', + field=models.CharField(default=1, max_length=300, verbose_name='description'), + preserve_default=False, + ), + migrations.AddField( + model_name='post', + name='status', + field=models.CharField(choices=[('df', 'Draft'), ('pb', 'Published')], default='df', max_length=2, verbose_name='status'), + ), + ] diff --git a/apps/blog/migrations/0003_alter_post_description.py b/apps/blog/migrations/0003_alter_post_description.py new file mode 100644 index 0000000..c1f99a5 --- /dev/null +++ b/apps/blog/migrations/0003_alter_post_description.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.1 on 2024-11-13 10:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('blog', '0002_post_description_post_status'), + ] + + operations = [ + migrations.AlterField( + model_name='post', + name='description', + field=models.CharField(blank=True, max_length=300, null=True, verbose_name='description'), + ), + ] diff --git a/apps/blog/migrations/0004_alter_post_is_active.py b/apps/blog/migrations/0004_alter_post_is_active.py new file mode 100644 index 0000000..2358e66 --- /dev/null +++ b/apps/blog/migrations/0004_alter_post_is_active.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.1 on 2024-11-13 10:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('blog', '0003_alter_post_description'), + ] + + operations = [ + migrations.AlterField( + model_name='post', + name='is_active', + field=models.BooleanField(default=True, verbose_name='active'), + ), + ] diff --git a/apps/blog/models.py b/apps/blog/models.py index fe9569a..75c7a0c 100644 --- a/apps/blog/models.py +++ b/apps/blog/models.py @@ -4,17 +4,30 @@ from django.utils.text import slugify from apps.shared.models import TimestempedAbstractModel from apps.shared.utils import get_random_text +from .managers import PublishedManager +from .choices import StatusChoice class Post(TimestempedAbstractModel): + title = models.CharField(_("title"), max_length=120, db_index=True) slug = models.SlugField(_("slug"), max_length=255, unique=True, db_index=True) + status = models.CharField( + _("status"), + max_length=2, + choices=StatusChoice.choices, + default=StatusChoice.DRAFT.value + ) + description = models.CharField(_("description"), max_length=300, blank=True, null=True) content = models.TextField(_("content")) publisher_at = models.DateField(_("publisher at")) - is_active = models.BooleanField(_("active"), default=False) + is_active = models.BooleanField(_("active"), default=True) author = models.ForeignKey("users.User", models.CASCADE, "posts", db_index=True) watching = models.BigIntegerField(_("watching"), default=0) + objects = models.Manager() + published = PublishedManager() + def delete(self, *args, **kwargs): print(self.post_comments.all().delete()) return super().delete(*args, **kwargs) diff --git a/apps/blog/views.py b/apps/blog/views.py index 31af7fd..d0e3f74 100644 --- a/apps/blog/views.py +++ b/apps/blog/views.py @@ -9,8 +9,7 @@ from apps.users.models import User from .forms import ( - PostUpdateForm, - PostCreateForm, + PostCreateUpdateForm, SettingsUserForm, SettingsUserProfileForm, ) @@ -42,9 +41,9 @@ class HomePageView(TemplateView): def get(self, request): if request.user is not None and request.user.is_authenticated: - posts = Post.objects.exclude(author=request.user) + posts = Post.published.exclude(author=request.user) else: - posts = Post.objects.all() + posts = Post.published.all() search_query = request.GET.get("search_query", None) page = request.GET.get("page", 1) @@ -74,6 +73,7 @@ class PostDetailPageView(View): def get(self, request, slug): post = get_object_or_404(Post, slug=slug) + post_comments = post.post_comments.all().order_by("-created_at") post.watching += 1 post.save() @@ -88,17 +88,18 @@ class PostCreatePageView(LoginRequiredMixin, TemplateView): template_name = "blog/post_create.html" def get(self, request): - return render(request, "blog/post_create.html", {"form": PostCreateForm()}) + return render(request, "blog/post_create.html", {"form": PostCreateUpdateForm()}) def post(self, request): - form = PostCreateForm(request.POST) + form = PostCreateUpdateForm(request.POST) if form.is_valid(): cd = form.cleaned_data post = Post.objects.create( title=cd.get("title"), + status=cd.get("status"), + description=cd.get("description"), content=cd.get("content"), - is_active=cd.get("is_active"), author=request.user, publisher_at=datetime.datetime.now().strftime("%Y-%m-%d"), ) @@ -122,7 +123,7 @@ def get(self, request): search_query_for_user_posts = request.GET.get( "search_query_for_user_posts", None ) - posts = Post.objects.filter(author=request.user) + posts = Post.objects.filter(author=request.user, is_active=True) if search_query_for_user_posts is not None: posts = get_search_model_queryset(posts, search_query_for_user_posts) @@ -134,13 +135,14 @@ class PostUpdateView(LoginRequiredMixin, View): template_name = "blog/post_update.html" def get(self, request, slug): - post = get_object_or_404(Post, slug=slug) - form = PostUpdateForm(instance=post) + post = get_object_or_404(Post, slug=slug, is_active=True) + + form = PostCreateUpdateForm(instance=post) return render(request, "blog/post_update.html", {"form": form, "post": post}) def post(self, request, slug): post = get_object_or_404(Post, slug=slug) - form = PostUpdateForm(request.POST, instance=post) + form = PostCreateUpdateForm(request.POST, instance=post) if form.is_valid(): form.save() messages.success(request, "Post succsessfully updated") @@ -156,8 +158,8 @@ class PostDeletePageView(LoginRequiredMixin, DeleteView): def post(self, request, slug): post = get_object_or_404(Post, slug=slug) - messages.success(request, "post successfully deleted") post.delete() + messages.success(request, "post successfully deleted") return redirect("blog:user_posts") @@ -214,7 +216,6 @@ def post_message(request, slug): return redirect(reverse("users:login")) post_message_input = request.GET.get("post_message_input", None) - print(post_message_input) if post_message_input is not None: set_post_comment(request.user, slug, post_message_input) diff --git a/apps/users/views.py b/apps/users/views.py index 8e1008f..920f822 100644 --- a/apps/users/views.py +++ b/apps/users/views.py @@ -84,7 +84,7 @@ class UserProfilePageView(View): def get(self, request, username): user = get_object_or_404(User, username=username) - posts = Post.objects.filter(author=user, is_active=True).all().order_by("id") + posts = Post.published.filter(author=user).all().order_by("-created_at") search_query = request.GET.get("search_query_for_user_profile", None) diff --git a/templates/blog/home.html b/templates/blog/home.html index 38a1f0c..c69b228 100644 --- a/templates/blog/home.html +++ b/templates/blog/home.html @@ -31,8 +31,13 @@
{{ massage }}

{{ post.title }}

{{ post.publisher_at }}
-

{{ post.content|truncatewords_html:30 }}

+

{{ post.description|truncatewords_html:30 }}

Continue reading +
+ {{ post.watching }} + {{ post.like_count }} + {{ post.comment_count }} +
diff --git a/templates/blog/post_create.html b/templates/blog/post_create.html index 451240f..3b30ce8 100644 --- a/templates/blog/post_create.html +++ b/templates/blog/post_create.html @@ -3,36 +3,42 @@ {% block title %} New post {% endblock %} {% block content %} -
-
-
-
-
- {% csrf_token %} -
- Blog Post - -
- - {{ form.title }} -
- -
- - {{ form.content }} -
-
- {{ form.is_active }} - Is active -
-
-
- -
-
-
-
- {% include 'components/latest_posts.html' %} -
-
+
+
+
+
+
+ {% csrf_token %} +
+ Blog Post + + +
+ + {{ form.title }} +
+ +
+ + {{ form.description }} +
+ +
+ + {{ form.content }} +
+
+ + {{ form.status }} +
+
+
+ +
+
+
+
+ {% include 'components/latest_posts.html' %} +
+
{% endblock %} \ No newline at end of file diff --git a/templates/blog/post_detail.html b/templates/blog/post_detail.html index 8c09eb0..0f6df8e 100644 --- a/templates/blog/post_detail.html +++ b/templates/blog/post_detail.html @@ -10,7 +10,11 @@

{{ post.title }}

{{ post.publisher_at }}by {{ post.author }}

- + +
+

{{ post.description }}

+
+

{{ post.content|markdown|safe }}

diff --git a/templates/blog/post_update.html b/templates/blog/post_update.html index 59a98ed..6830792 100644 --- a/templates/blog/post_update.html +++ b/templates/blog/post_update.html @@ -16,13 +16,18 @@ {{ form.title }} +
+ + {{ form.description }} +
+
{{ form.content }}
-
- {{ form.is_active }} - Is active +
+ + {{ form.status }}
diff --git a/templates/blog/profile.html b/templates/blog/profile.html index ae76ee4..b3b1386 100644 --- a/templates/blog/profile.html +++ b/templates/blog/profile.html @@ -58,16 +58,16 @@

+ +

{{ post.title }}

+
{{ post.publisher_at }}
+

{{ post.description|truncatewords_html:30 }}

+ Continue reading
{{ post.watching }} {{ post.like_count }} {{ post.comment_count }}
- -

{{ post.title }}

-
{{ post.publisher_at }}
-

{{ post.content|truncatewords_html:30 }}

- Continue reading

diff --git a/templates/blog/user_posts.html b/templates/blog/user_posts.html index 0139aa9..a3aa264 100644 --- a/templates/blog/user_posts.html +++ b/templates/blog/user_posts.html @@ -30,14 +30,14 @@
{{ message }}
- {% if post.is_active %} - Design - {% else %} - World + {% if post.status == 'pb' %} + Published + {% elif post.status == 'df' %} + Draft {% endif %}

{{ post.title }}

{{ post.publisher_at }}
-

{{ post.content|truncatewords_html:30 }}

+

{{ post.description|truncatewords_html:30 }}

Continue reading