Add post and comment creation functionality
This commit is contained in:
parent
c6ba3dd6b0
commit
d23cdff3af
18
PlanktonDetector/Community/froms.py
Normal file
18
PlanktonDetector/Community/froms.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from django import forms
|
||||||
|
from .models import Post, Comment
|
||||||
|
|
||||||
|
|
||||||
|
class PostForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Post
|
||||||
|
fields = ["title", "content"]
|
||||||
|
|
||||||
|
|
||||||
|
class CommentForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Comment
|
||||||
|
fields = ["content"]
|
||||||
|
labels = {
|
||||||
|
"content": "Comment",
|
||||||
|
}
|
||||||
|
widgets = {"content": forms.Textarea(attrs={"rows": 3})}
|
@ -0,0 +1,21 @@
|
|||||||
|
# Generated by Django 4.2.7 on 2024-01-05 19:44
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("Community", "0002_post_date_pub"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="comment",
|
||||||
|
name="date_added",
|
||||||
|
field=models.DateField(
|
||||||
|
auto_now_add=True, default=django.utils.timezone.now
|
||||||
|
),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
@ -10,8 +10,13 @@ class Post(models.Model):
|
|||||||
author = models.ForeignKey(User, on_delete=models.CASCADE, null=False)
|
author = models.ForeignKey(User, on_delete=models.CASCADE, null=False)
|
||||||
date_pub = models.DateField(auto_now_add=True)
|
date_pub = models.DateField(auto_now_add=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sorted_comments(self):
|
||||||
|
return self.comment_set.all().order_by("-date_added")
|
||||||
|
|
||||||
|
|
||||||
class Comment(models.Model):
|
class Comment(models.Model):
|
||||||
author = models.ForeignKey(User, on_delete=models.CASCADE, null=False)
|
author = models.ForeignKey(User, on_delete=models.CASCADE, null=False)
|
||||||
content = models.TextField()
|
content = models.TextField()
|
||||||
post = models.ForeignKey(Post, on_delete=models.CASCADE)
|
post = models.ForeignKey(Post, on_delete=models.CASCADE)
|
||||||
|
date_added = models.DateField(auto_now_add=True)
|
||||||
|
@ -6,4 +6,5 @@ from . import views
|
|||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("posts/", views.ListPosts.as_view(), name="posts"),
|
path("posts/", views.ListPosts.as_view(), name="posts"),
|
||||||
path("posts/<int:pk>", views.PostDetails.as_view(), name="post-details"),
|
path("posts/<int:pk>", views.PostDetails.as_view(), name="post-details"),
|
||||||
|
path("posts/create/", views.AddPost.as_view(), name="add_post"),
|
||||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
from django.shortcuts import render
|
from typing import Any
|
||||||
|
from django.db.models.query import QuerySet
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.shortcuts import redirect, render
|
||||||
from django.views.generic import ListView, DetailView
|
from django.views.generic import ListView, DetailView
|
||||||
|
from django.views.generic.edit import FormView, FormMixin
|
||||||
from .models import Post, Comment
|
from .models import Post, Comment
|
||||||
|
from .froms import PostForm, CommentForm
|
||||||
|
|
||||||
|
|
||||||
# Create your views here.
|
# Create your views here.
|
||||||
|
|
||||||
@ -8,8 +14,44 @@ from .models import Post, Comment
|
|||||||
class ListPosts(ListView):
|
class ListPosts(ListView):
|
||||||
model = Post
|
model = Post
|
||||||
template_name = "list_posts.html"
|
template_name = "list_posts.html"
|
||||||
|
paginate_by = 3
|
||||||
|
|
||||||
|
def get_queryset(self) -> QuerySet[Any]:
|
||||||
|
queryset = Post.objects.all().order_by("-date_pub")
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
class PostDetails(DetailView):
|
class PostDetails(DetailView, FormMixin):
|
||||||
model = Post
|
model = Post
|
||||||
template_name = "post_details.html"
|
template_name = "post_details.html"
|
||||||
|
form_class = CommentForm
|
||||||
|
|
||||||
|
# def get_queryset(self) -> QuerySet[Any]:
|
||||||
|
# queryset = Post.objects.get(pk=self.request.GET.get("pk"))
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
self.object = self.get_object()
|
||||||
|
form = self.get_form()
|
||||||
|
if form.is_valid():
|
||||||
|
return self.form_valid(form)
|
||||||
|
else:
|
||||||
|
return self.form_invalid(form)
|
||||||
|
|
||||||
|
def form_valid(self, form) -> HttpResponse:
|
||||||
|
post = self.get_object()
|
||||||
|
new_comment = form.save(commit=False)
|
||||||
|
new_comment.author = self.request.user
|
||||||
|
new_comment.post = post
|
||||||
|
new_comment.save()
|
||||||
|
return redirect("post-details", pk=post.id)
|
||||||
|
|
||||||
|
|
||||||
|
class AddPost(FormView):
|
||||||
|
template_name = "add_post.html"
|
||||||
|
form_class = PostForm
|
||||||
|
|
||||||
|
def form_valid(self, form) -> HttpResponse:
|
||||||
|
new_post = form.save(commit=False)
|
||||||
|
new_post.author = self.request.user
|
||||||
|
new_post.save()
|
||||||
|
return redirect("post-details", pk=new_post.id)
|
||||||
|
@ -10,5 +10,5 @@ urlpatterns = [
|
|||||||
path(
|
path(
|
||||||
"detection/<int:pk>", views.DetectionDetails.as_view(), name="detection-details"
|
"detection/<int:pk>", views.DetectionDetails.as_view(), name="detection-details"
|
||||||
),
|
),
|
||||||
path("export_pred/<int:pk>", views.download_pred_res, name="download_predicitons"),
|
path("export_pred/<int:pk>", views.download_pred_res, name="download_predicitions"),
|
||||||
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
from django.shortcuts import render, redirect
|
from django.shortcuts import render, redirect
|
||||||
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
|
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
|
||||||
from django.contrib.auth import authenticate, login, logout
|
from django.contrib.auth import authenticate, login, logout
|
||||||
|
from django.urls import reverse_lazy
|
||||||
from django.views import View
|
from django.views import View
|
||||||
|
|
||||||
|
|
||||||
class LoginView(View):
|
class LoginView(View):
|
||||||
form_class = AuthenticationForm
|
form_class = AuthenticationForm
|
||||||
template_name = "login_signup.html"
|
template_name = "login_signup.html"
|
||||||
@ -12,6 +14,7 @@ class LoginView(View):
|
|||||||
return render(request, self.template_name, {"form": form})
|
return render(request, self.template_name, {"form": form})
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
|
redirection_path = request.GET.get("next")
|
||||||
form = self.form_class(data=request.POST)
|
form = self.form_class(data=request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
user = authenticate(
|
user = authenticate(
|
||||||
@ -20,7 +23,10 @@ class LoginView(View):
|
|||||||
)
|
)
|
||||||
if user is not None:
|
if user is not None:
|
||||||
login(request, user)
|
login(request, user)
|
||||||
return redirect("detect")
|
if redirection_path is not None:
|
||||||
|
return redirect(redirection_path)
|
||||||
|
else:
|
||||||
|
return redirect("detect/")
|
||||||
return render(request, self.template_name, {"form": form})
|
return render(request, self.template_name, {"form": form})
|
||||||
|
|
||||||
|
|
||||||
@ -37,10 +43,14 @@ class SignupView(View):
|
|||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
user = form.save()
|
user = form.save()
|
||||||
login(request, user)
|
login(request, user)
|
||||||
return redirect("detect")
|
return redirect("detect/")
|
||||||
return render(request, self.template_name, {"form": form, "signup": True})
|
return render(request, self.template_name, {"form": form, "signup": True})
|
||||||
|
|
||||||
|
|
||||||
def logout_view(request):
|
def logout_view(request):
|
||||||
|
redirection_path = request.GET.get("next")
|
||||||
logout(request)
|
logout(request)
|
||||||
return redirect("detect")
|
if redirection_path is not None:
|
||||||
|
return redirect(redirection_path)
|
||||||
|
else:
|
||||||
|
return redirect("detect/")
|
||||||
|
Binary file not shown.
0
PlanktonDetector/static/Community/css/add_post.css
Normal file
0
PlanktonDetector/static/Community/css/add_post.css
Normal file
@ -27,6 +27,7 @@ ul{
|
|||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 25px;
|
margin-bottom: 25px;
|
||||||
|
box-shadow: 0 2px lightgray;
|
||||||
}
|
}
|
||||||
|
|
||||||
.date_author{
|
.date_author{
|
||||||
@ -35,4 +36,16 @@ ul{
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
#add_post{
|
||||||
|
text-align: center;
|
||||||
|
padding: 1px 6px 1px 6px;
|
||||||
|
width: 10%;
|
||||||
|
background-color: chartreuse;
|
||||||
|
border-radius: 25px;
|
||||||
|
border: 2px black;
|
||||||
|
box-shadow: 0 0 0 2px black;
|
||||||
|
font-size: 20px;
|
||||||
|
font-family: Arial;
|
||||||
}
|
}
|
@ -77,9 +77,46 @@ body, html{
|
|||||||
margin-top:10px;
|
margin-top:10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#comment-author{
|
#add_comment{
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
flex-direction: row;
|
||||||
justify-content: flex-end;
|
width: 55%;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
align-items: center;
|
||||||
|
margin-top:30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#comment_form{
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#comment_form textarea{
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#comment_form label{
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date_author{
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
#submit{
|
||||||
|
background-color: chartreuse;
|
||||||
|
border-radius: 25px;
|
||||||
|
border: 2px solid black;
|
||||||
|
font-size: 20px;
|
||||||
|
text-decoration: none;
|
||||||
|
color:black;
|
||||||
|
font-family: Arial;
|
||||||
|
text-align: center;
|
||||||
|
}
|
@ -29,17 +29,6 @@ form{
|
|||||||
margin: 10px 0px 15px 0px;
|
margin: 10px 0px 15px 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#submit{
|
|
||||||
background-color: chartreuse;
|
|
||||||
margin-top: auto;
|
|
||||||
height: 50px;
|
|
||||||
width: 80%;
|
|
||||||
border-radius: 25px;
|
|
||||||
border: 2px white;
|
|
||||||
box-shadow: 0 0 0 4px white;
|
|
||||||
font-size: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.upload_field {
|
.upload_field {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -91,7 +80,26 @@ form{
|
|||||||
height: 50px;
|
height: 50px;
|
||||||
width: 60%;
|
width: 60%;
|
||||||
border-radius: 25px;
|
border-radius: 25px;
|
||||||
border: 2px white;
|
border: 2px solid black;
|
||||||
box-shadow: 0 0 0 4px white;
|
|
||||||
font-size: 25px;
|
font-size: 25px;
|
||||||
|
text-decoration: none;
|
||||||
|
color:black;
|
||||||
|
font-family: Arial;
|
||||||
|
text-align: center;
|
||||||
|
line-height:45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#submit{
|
||||||
|
background-color: chartreuse;
|
||||||
|
margin-top: auto;
|
||||||
|
height: 50px;
|
||||||
|
width: 80%;
|
||||||
|
border-radius: 25px;
|
||||||
|
border: 2px solid white;
|
||||||
|
font-size: 25px;
|
||||||
|
text-decoration: none;
|
||||||
|
color:black;
|
||||||
|
font-family: Arial;
|
||||||
|
text-align: center;
|
||||||
|
line-height:45px;
|
||||||
}
|
}
|
@ -34,9 +34,13 @@ form{
|
|||||||
height: 50px;
|
height: 50px;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
border-radius: 25px;
|
border-radius: 25px;
|
||||||
border: 2px white;
|
border: 2px solid white;
|
||||||
box-shadow: 0 0 0 4px white;
|
|
||||||
font-size: 25px;
|
font-size: 25px;
|
||||||
|
text-decoration: none;
|
||||||
|
color:black;
|
||||||
|
font-family: Arial;
|
||||||
|
text-align: center;
|
||||||
|
line-height:45px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload_field {
|
.upload_field {
|
||||||
|
12
PlanktonDetector/templates/add_post.html
Normal file
12
PlanktonDetector/templates/add_post.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{%extends 'base.html'%}
|
||||||
|
{%load static%}
|
||||||
|
{%block extracss%}
|
||||||
|
<link rel="stylesheet" href="{% static 'Community/css/add_post.css' %}">
|
||||||
|
{%endblock extracss%}
|
||||||
|
{%block content%}
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.as_p }}
|
||||||
|
<input type="submit" value="Add post">
|
||||||
|
</form>
|
||||||
|
{%endblock content%}
|
@ -18,9 +18,9 @@
|
|||||||
<a href="{% url 'detect' %}">Predict</a>
|
<a href="{% url 'detect' %}">Predict</a>
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<a href="{% url 'history' %}">History</a>
|
<a href="{% url 'history' %}">History</a>
|
||||||
<a href="{% url 'logout' %}">Logout {{user}}</a>
|
<a href="{% url 'logout' %}?next={{request.path}}">Logout {{user}}</a>
|
||||||
{%else%}
|
{%else%}
|
||||||
<a href="{% url 'login' %}">Login</a>
|
<a href="{% url 'login' %}?next={{request.path}}">Login</a>
|
||||||
{%endif%}
|
{%endif%}
|
||||||
</div>
|
</div>
|
||||||
{%block content%}{%endblock content%}
|
{%block content%}{%endblock content%}
|
||||||
|
@ -6,7 +6,10 @@
|
|||||||
{%block content%}
|
{%block content%}
|
||||||
<div class="post_list">
|
<div class="post_list">
|
||||||
<h2>Posts</h2>
|
<h2>Posts</h2>
|
||||||
{% for post in object_list %}
|
{% if user.is_authenticated %}
|
||||||
|
<a href="{%url 'add_post'%}" id="add_post">Add new post</a>
|
||||||
|
{%endif%}
|
||||||
|
{% for post in page_obj %}
|
||||||
<div class="post_details">
|
<div class="post_details">
|
||||||
<a href="{%url 'post-details' pk=post.id %}">
|
<a href="{%url 'post-details' pk=post.id %}">
|
||||||
<h3 class="title">{{ post.title }}</h3>
|
<h3 class="title">{{ post.title }}</h3>
|
||||||
@ -17,5 +20,22 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
<div class="pagination">
|
||||||
|
<span class="step-links">
|
||||||
|
{% if page_obj.has_previous %}
|
||||||
|
<a href="?page=1">« first</a>
|
||||||
|
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<span class="current">
|
||||||
|
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{% if page_obj.has_next %}
|
||||||
|
<a href="?page={{ page_obj.next_page_number }}">next</a>
|
||||||
|
<a href="?page={{ page_obj.paginator.num_pages }}">last »</a>
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{%endblock content%}
|
{%endblock content%}
|
@ -14,12 +14,22 @@
|
|||||||
<p id="author">{{object.author}}</p>
|
<p id="author">{{object.author}}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="add_comment">
|
||||||
|
<form method="POST" id="comment_form">
|
||||||
|
{%csrf_token%}
|
||||||
|
{{form}}
|
||||||
|
<input type="submit" id="submit" value="Add comment">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
<p id="comment-label">Comments:</p>
|
<p id="comment-label">Comments:</p>
|
||||||
<div class="comments">
|
<div class="comments">
|
||||||
{%for comments in object.comment_set.all%}
|
{%for comment in object.sorted_comments%}
|
||||||
<div class="single-comment">
|
<div class="single-comment">
|
||||||
<p id="comment-content">{{comments.content}}</p>
|
<p id="comment-content">{{comment.content}}</p>
|
||||||
<p id="comment-author">{{comments.author}}</p>
|
<div class="date_author">
|
||||||
|
<p id="comment-date">{{comment.date_added}}</p>
|
||||||
|
<p id="comment-author">{{comment.author}}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{%endfor%}
|
{%endfor%}
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<div class="side_menu">
|
<div class="side_menu">
|
||||||
{% if not user.is_authenticated %}
|
{% if not user.is_authenticated %}
|
||||||
<p id="description">Please login to see results</p>
|
<p id="description">Please login to see results</p>
|
||||||
<button onclick="location.href='{% url 'login' %}'" id='submit' method="GET" type='button'>Login</button>
|
<a href="{% url 'login'%}?next={{request.path}}" id="submit">Login</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p id="description">Results:</p>
|
<p id="description">Results:</p>
|
||||||
{%for img in img.images.all %}
|
{%for img in img.images.all %}
|
||||||
@ -20,7 +20,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{%endfor%}
|
{%endfor%}
|
||||||
{%endfor%}
|
{%endfor%}
|
||||||
<button onclick="location.href='{% url 'detect' %}'" id='submit' method="GET" type='button'>Submit again</button>
|
<a href="{% url 'detect' %}" id="submit">Submit again</a>
|
||||||
{%endif%}
|
{%endif%}
|
||||||
</div>
|
</div>
|
||||||
<div class="upload_field">
|
<div class="upload_field">
|
||||||
@ -29,7 +29,7 @@
|
|||||||
<img src="{{img.image.url}}" alt="upload_image" style="width:640px; height:460px;">
|
<img src="{{img.image.url}}" alt="upload_image" style="width:640px; height:460px;">
|
||||||
{%endfor%}
|
{%endfor%}
|
||||||
</label>
|
</label>
|
||||||
<button onclick="location.href='{%url 'download_predicitons' pk=img.id%}'" id="download_json" method="GET" type="button">Export predictions</button>
|
<a href="{%url 'download_predicitions' pk=img.id %}" id="download_json">Export predictions</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<script>
|
<script>
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
<div class="side_menu">
|
<div class="side_menu">
|
||||||
{% if not user.is_authenticated %}
|
{% if not user.is_authenticated %}
|
||||||
<p id="description">Please login to submit image</p>
|
<p id="description">Please login to submit image</p>
|
||||||
<button onclick="location.href='{% url 'login' %}'" id='submit' method="GET" type='button'>Login</button>
|
<a href="{% url 'login' %}?next={{request.path}}" id="submit">Login</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p id="description">Choose image for analysis</p>
|
<p id="description">Choose image for analysis</p>
|
||||||
<input type="submit" id="submit" value="Submit">
|
<input type="submit" id="submit" value="Submit">
|
||||||
@ -71,10 +71,8 @@ document.getElementById('id_image').addEventListener('change', function () {
|
|||||||
var user = `{{user.is_authenticated}}`
|
var user = `{{user.is_authenticated}}`
|
||||||
var upload = document.getElementById("id_image")
|
var upload = document.getElementById("id_image")
|
||||||
if (user.toLowerCase()=="false"){
|
if (user.toLowerCase()=="false"){
|
||||||
console.log('disabled');
|
|
||||||
upload.disabled=true;
|
upload.disabled=true;
|
||||||
}else if (user.toLowerCase()=="true"){
|
}else if (user.toLowerCase()=="true"){
|
||||||
console.log("enabled");
|
|
||||||
upload.disabled=false;
|
upload.disabled=false;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
Loading…
Reference in New Issue
Block a user