Commit fd98d12d authored by Tang, Alexander's avatar Tang, Alexander
Browse files

Merge remote-tracking branch 'remotes/origin/master' into Alex_2020_06_12

# Conflicts:
#	.gitignore
#	blog/models.py
#	blog/static/blog/all_blogs.js
#	blog/templates/blog/all_blogs.html
#	blog/urls.py
#	blog/views.py
#	djangoblog.sqlite3
parents 44d44934 8e62d75a
from time import timezone
import uuid
from ckeditor.fields import RichTextField
from ckeditor_uploader.fields import RichTextUploadingField
from django.db import models
from datetime import datetime
from django.utils.text import slugify
ARTICLE_STATE_HIDE_FROM_LIST = 1
BLOG_STATE_HIDE_FROM_LIST = 1
BLOG_STATE_CAN_HAVE_COMMENTS = 2
# Create your models here.
class Blog(models.Model):
identifier = models.CharField(max_length=500, unique=True, default=uuid.uuid4())
title = models.CharField(default="Titel", max_length=200)
description = models.TextField(blank=True)
date = models.DateTimeField(default=datetime.now, blank=True)
# need to install pillow for images
image = models.ImageField(upload_to='images/', blank=True)
created = models.DateField(auto_now_add=True)
edited = models.DateField(auto_now=True)
status = models.IntegerField(default=1)
#TODO URL-Field is currently not used
slug = models.SlugField(max_length=300, unique=True, null=False)
def __str__(self):
return self.identifier + " (" + self.title + ")"
def get_absolute_url(self):
# Slugify the combination of role and company_name as these may contain
# whitespace or other characters that are not permitted in urls.
slug = slugify(f"{self.url}")
# "blog-detail" refers to url, key word argument
return reverse("blog-detail", kwargs={'slug': slug})
\ No newline at end of file
""" Blog class
One blog is a single block of content which can be shown on our page
"""
identifier = models.CharField(max_length=500, unique=True, default=uuid.uuid4())
title = models.CharField(default="Titel", max_length=200)
description = RichTextUploadingField(blank=True)
date = models.DateTimeField(default=datetime.now, blank=True)
# need to install pillow for images
image = models.ImageField(upload_to='images/', blank=True)
created = models.DateField(auto_now_add=True)
edited = models.DateField(auto_now=True)
status = models.IntegerField(default=1)
# TODO URL-Field is currently not used
slug = models.SlugField(max_length=300, unique=True, null=False)
def __str__(self):
return self.identifier + " (" + self.title + ")"
def is_hidden_in_blog_list(self):
""" Returns true or false if this blog is open for comments """
return self.status & BLOG_STATE_HIDE_FROM_LIST == BLOG_STATE_HIDE_FROM_LIST
def are_comments_allowed(self):
""" Returns true or false if this blog is open for comments """
return self.status & BLOG_STATE_CAN_HAVE_COMMENTS == BLOG_STATE_CAN_HAVE_COMMENTS
def get_absolute_url(self):
# Slugify the combination of role and company_name as these may contain
# whitespace or other characters that are not permitted in urls.
slug = slugify(f"{self.url}")
# "blog-detail" refers to url, key word argument
return reverse("blog-detail", kwargs={'slug': slug})
class Comment(models.Model):
""" Comment class
Linked to a blog, one blog can have many comments from external users
"""
author = models.TextField(max_length=150)
text = models.TextField(max_length=1000)
date = models.DateTimeField(default=datetime.now)
related_blog = models.ForeignKey(Blog, on_delete=models.SET_NULL, null=True)
def __str__(self):
return self.related_blog.identifier + ': ' + self.text
.comment-area {
border-top: 1px solid #0B9ED9;
border-radius: 0;
}
.comment-area article {
background-color: #eeeeee;
padding: 1em;
border-radius: 3px;
margin-bottom: 1em;
}
.comment-area .author {
font-weight: bold;
margin-bottom: 0.5em;
}
.comment-area .author:after {
content: ":"
}
.comment-area blockquote{
background-color: #ffffff;
padding: 0.3em;
}
.comment-area blockquote:before {
content: "„"
}
.comment-area blockquote:after {
content: "“"
}
.comment-area .no-comment{
font-style: italic;
}
.comment-area label:after{
content: ":"
}
\ No newline at end of file
......@@ -24,7 +24,6 @@ body {
margin-top: 1em;
margin-bottom: 1em;
border-radius: 5px;
box-shadow: 3px 3px 3px #010748;
padding: 1em;
}
......
......@@ -7,6 +7,11 @@
{% bootstrap_css %}
<link rel="stylesheet" href="{% static 'blog/styles.css' %}">
<script src="{% static 'blog/all_blogs.js' %}"></script>
<style>
.card-body {
cursor: pointer;
}
</style>
<meta charset="UTF-8">
<title>Title</title>
</head>
......@@ -24,13 +29,15 @@
<div class="card-deck">
{% for blog in blogs %}
<div class="card float-left mt-3" style="width: 18rem; min-width: 18rem; max-width: 18rem" data-value="{{ blog.slug }}">
{% if blog.image.url %}
{% if blog.image and blog.image.url %}
<img class="card-img-top" src="{{ blog.image.url }}" alt="Card image cap">
{% endif %}
<div class="card-body">
<div class="text-right minidate" >{{ blog.date }}</div>
<h3 class="card-title text-center">{{ blog.title }}</h3>
{% autoescape off %}
<p class="card-text">{{ blog.description|truncatechars:200 }}</p>
{% endautoescape %}
<div id="cardid" class="text-right minidate" >{{ blog.slug }}</div>
{% if blog.url %}
<div class="card-footer">
......@@ -41,6 +48,12 @@
</div>
{% endfor %}
</div>
<footer class="fixed-bottom bg-light">
<ul class="nav justify-content-end">
<li class="nav-item"><a class="nav-link" href="{% url 'blog:detail' blog_id=2 %}">Impressum</a></li>
<li class="nav-item"><a class="nav-link" href="{% url 'blog:detail' blog_id=3 %}">Kontakt</a></li>
</ul>
</footer>
</div>
......
......@@ -4,6 +4,7 @@
<html lang="en">
<head>
<link rel="stylesheet" href="{% static 'blog/styles.css' %}">
<link rel="stylesheet" href="{% static 'blog/comment-area.css' %}">
<meta charset="UTF-8">
<title>Title</title>
{% bootstrap_javascript %}
......@@ -18,16 +19,29 @@
</div>
<div class="container">
{% bootstrap_messages %}
<div class="text-right minidate" >{{ blog.date }}</div>
{% autoescape off %}
<p class="card-text">{{ blog.description }}</p>
{% endautoescape %}
<a href="{% url 'blog:all_blogs' %}" class="btn btn-primary">Back to Blogs</a>
<br>
{% if blog.image.url %}
<div class="container thirtypercent float-right">
{% if blog.image and blog.image.url %}
<div class="container thirtypercent">
<img class="card-img-top" src="{{ blog.image.url }}" alt="Card image cap">
</div>
{% endif %}
</div>
{% block comments %}
<!-- Comments will go in here -->
{% endblock %}
<footer class="fixed-bottom bg-light">
<ul class="nav justify-content-end">
<li class="nav-item"><a class="nav-link" href="{% url 'blog:detail' blog_id=2 %}">Impressum</a></li>
<li class="nav-item"><a class="nav-link" href="{% url 'blog:detail' blog_id=3 %}">Kontakt</a></li>
</ul>
</footer>
</body>
</html>
\ No newline at end of file
{% extends 'blog/detail.html' %}
{% load bootstrap4 %}
{% block comments %}
<div class="container comment-area">
<h4>Kommentare</h4>
<!-- Show existing comments -->
{% for comment in comments %}
<article>
<div class="row">
<div class="col author">{{comment.author}}</div>
<div class="col text-right minidate">{{comment.date}}</div>
</div>
<blockquote class="col">{{comment.text}}</blockquote>
</article>
{% endfor %}
{% if comments.count == 0 %}
<div class="row">
<div class="col no-comment">Es wurden noch keine Kommentare für diesen Artikel abgegeben.</div>
</div>
{% endif %}
<!-- Add new comments -->
<h5>Schreibe einen Kommentar</h5>
<form action="{% url 'blog:save_comment' %}" method="POST">
{% csrf_token %}
{% bootstrap_form comment_form %}
<input type="submit" class="btn btn-primary">
</form>
</div>
{% endblock %}
\ No newline at end of file
......@@ -24,10 +24,8 @@ app_name = 'blog'
urlpatterns = [
path('', views.all_blogs, name='all_blogs'),
# Match urls based on ID
#path('<int:blog_id>/', views.detail, name='detail-id'),
path('<slug:slug>', BlogDetailView.as_view(), name='blog-detail'),
# Match urls based on keywords (for SEO) and ID
#path('<int:blog_id>/<slug:slug>', views.detail, name='detail-keywords'),
path('comment/save', views.save_comment, name='save_comment')
]
\ No newline at end of file
from django.shortcuts import render, get_object_or_404
from .models import Blog
from django.http import HttpResponseNotAllowed
from django.contrib import messages
from django.shortcuts import render, get_object_or_404, redirect
from django.urls import reverse_lazy
from .forms import CommentForm
from .models import *
from django.views.generic import ListView, DetailView
# Create your views here.
def all_blogs(request):
blogs = Blog.objects.all()
return render(request, 'blog/all_blogs.html',{'blogs':blogs})
# Exclude those with status hidden from the overall list
query = f'status & {BLOG_STATE_HIDE_FROM_LIST} <> {BLOG_STATE_HIDE_FROM_LIST}'
blogs = Blog.objects.extra(where=[query]).order_by('created')
return render(request, 'blog/all_blogs.html', {'blogs': blogs})
# TODO: Fallback
class BlogDetailView(DetailView):
......@@ -15,7 +24,32 @@ class BlogDetailView(DetailView):
# TODO wird nicht mehr aufgerufen
def detail(request, blog_id):
blog = get_object_or_404(Blog, pk=blog_id)
return render(request, 'blog/detail.html', {'blog':blog})
if blog.are_comments_allowed():
# load existing comments
comments = Comment.objects.select_related().filter(related_blog=blog.id)
# dialog for new comments
form = CommentForm(initial={'related_blog': blog.id})
# render with comments
return render(request, 'blog/detail_with_comments.html',
{'blog': blog, 'comments': comments, 'comment_form': form})
return render(request, 'blog/detail.html', {'blog': blog})
def save_comment(request):
if request.method != 'POST':
# no form submit - return 405
return HttpResponseNotAllowed(['POST'])
form = CommentForm(request.POST)
if form.is_valid():
form.save()
messages.success(request, 'Danke! Dein Kommentar wurde übermittelt.')
else:
messages.error(request, 'Ups! Da ist etwas schief gelaufen. Wir konnten deinen Kommentar nicht speichern!')
print(form.data.get('related_blog'))
blog = get_object_or_404(Blog, pk=form.data.get('related_blog'))
# back to the details page
# todo after merge: Redirect to url from blog article
return redirect(reverse_lazy('blog:detail', kwargs={'blog_id':blog.id}))
......@@ -40,6 +40,8 @@ INSTALLED_APPS = [
'blog',
'portfolio',
'bootstrap4',
'ckeditor',
'ckeditor_uploader',
]
MIDDLEWARE = [
......@@ -123,6 +125,13 @@ USE_TZ = True
STATIC_URL = '/static/'
PROJECT_DIR=os.path.dirname(__file__)
# TODO: STATIC_ROOT must be eventually edited
STATIC_ROOT = os.path.join(PROJECT_DIR,'static/')
CKEDITOR_UPLOAD_PATH = "uploads/"
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
......
select.admin-autocomplete {
width: 20em;
}
.select2-container--admin-autocomplete.select2-container {
min-height: 30px;
}
.select2-container--admin-autocomplete .select2-selection--single,
.select2-container--admin-autocomplete .select2-selection--multiple {
min-height: 30px;
padding: 0;
}
.select2-container--admin-autocomplete.select2-container--focus .select2-selection,
.select2-container--admin-autocomplete.select2-container--open .select2-selection {
border-color: #999;
min-height: 30px;
}
.select2-container--admin-autocomplete.select2-container--focus .select2-selection.select2-selection--single,
.select2-container--admin-autocomplete.select2-container--open .select2-selection.select2-selection--single {
padding: 0;
}
.select2-container--admin-autocomplete.select2-container--focus .select2-selection.select2-selection--multiple,
.select2-container--admin-autocomplete.select2-container--open .select2-selection.select2-selection--multiple {
padding: 0;
}
.select2-container--admin-autocomplete .select2-selection--single {
background-color: #fff;
border: 1px solid #ccc;
border-radius: 4px;
}
.select2-container--admin-autocomplete .select2-selection--single .select2-selection__rendered {
color: #444;
line-height: 30px;
}
.select2-container--admin-autocomplete .select2-selection--single .select2-selection__clear {
cursor: pointer;
float: right;
font-weight: bold;
}
.select2-container--admin-autocomplete .select2-selection--single .select2-selection__placeholder {
color: #999;
}
.select2-container--admin-autocomplete .select2-selection--single .select2-selection__arrow {
height: 26px;
position: absolute;
top: 1px;
right: 1px;
width: 20px;
}
.select2-container--admin-autocomplete .select2-selection--single .select2-selection__arrow b {
border-color: #888 transparent transparent transparent;
border-style: solid;
border-width: 5px 4px 0 4px;
height: 0;
left: 50%;
margin-left: -4px;
margin-top: -2px;
position: absolute;
top: 50%;
width: 0;
}
.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--single .select2-selection__clear {
float: left;
}
.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--single .select2-selection__arrow {
left: 1px;
right: auto;
}
.select2-container--admin-autocomplete.select2-container--disabled .select2-selection--single {
background-color: #eee;
cursor: default;
}
.select2-container--admin-autocomplete.select2-container--disabled .select2-selection--single .select2-selection__clear {
display: none;
}
.select2-container--admin-autocomplete.select2-container--open .select2-selection--single .select2-selection__arrow b {
border-color: transparent transparent #888 transparent;
border-width: 0 4px 5px 4px;
}
.select2-container--admin-autocomplete .select2-selection--multiple {
background-color: white;
border: 1px solid #ccc;
border-radius: 4px;
cursor: text;
}
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__rendered {
box-sizing: border-box;
list-style: none;
margin: 0;
padding: 0 5px;
width: 100%;
}
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__rendered li {
list-style: none;
}
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__placeholder {
color: #999;
margin-top: 5px;
float: left;
}
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__clear {
cursor: pointer;
float: right;
font-weight: bold;
margin: 5px;
}
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice {
background-color: #e4e4e4;
border: 1px solid #ccc;
border-radius: 4px;
cursor: default;
float: left;
margin-right: 5px;
margin-top: 5px;
padding: 0 5px;
}
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice__remove {
color: #999;
cursor: pointer;
display: inline-block;
font-weight: bold;
margin-right: 2px;
}
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice__remove:hover {
color: #333;
}
.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder, .select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-search--inline {
float: right;
}
.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
margin-left: 5px;
margin-right: auto;
}
.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
margin-left: 2px;
margin-right: auto;
}
.select2-container--admin-autocomplete.select2-container--focus .select2-selection--multiple {
border: solid #999 1px;
outline: 0;
}
.select2-container--admin-autocomplete.select2-container--disabled .select2-selection--multiple {
background-color: #eee;
cursor: default;
}
.select2-container--admin-autocomplete.select2-container--disabled .select2-selection__choice__remove {
display: none;
}
.select2-container--admin-autocomplete.select2-container--open.select2-container--above .select2-selection--single, .select2-container--admin-autocomplete.select2-container--open.select2-container--above .select2-selection--multiple {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.select2-container--admin-autocomplete.select2-container--open.select2-container--below .select2-selection--single, .select2-container--admin-autocomplete.select2-container--open.select2-container--below .select2-selection--multiple {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.select2-container--admin-autocomplete .select2-search--dropdown .select2-search__field {
border: 1px solid #ccc;
}
.select2-container--admin-autocomplete .select2-search--inline .select2-search__field {
background: transparent;
border: none;
outline: 0;
box-shadow: none;
-webkit-appearance: textfield;
}
.select2-container--admin-autocomplete .select2-results > .select2-results__options {
max-height: 200px;
overflow-y: auto;
}
.select2-container--admin-autocomplete .select2-results__option[role=group] {
padding: 0;
}
.select2-container--admin-autocomplete .select2-results__option[aria-disabled=true] {
color: #999;
}
.select2-container--admin-autocomplete .select2-results__option[aria-selected=true] {
background-color: #ddd;
}
.select2-container--admin-autocomplete .select2-results__option .select2-results__option {
padding-left: 1em;
}
.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__group {
padding-left: 0;
}
.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option {
margin-left: -1em;
padding-left: 2em;
}
.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -2em;
padding-left: 3em;
}
.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -3em;
padding-left: 4em;
}
.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -4em;
padding-left: 5em;
}
.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
margin-left: -5em;
padding-left: 6em;
}
.select2-container--admin-autocomplete .select2-results__option--highlighted[aria-selected] {
background-color: #79aec8;
color: white;
}
.select2-container--admin-autocomplete .select2-results__group {
cursor: default;
display: block;
padding: 6px;