├── simpleblog
├── migrations
│ ├── __init__.py
│ └── 0001_initial.py
├── templatetags
│ ├── __init__.py
│ └── blog_tags.py
├── locale
│ ├── fr_FR
│ │ └── LC_MESSAGES
│ │ │ ├── django.mo
│ │ │ └── django.po
│ └── sv_SE
│ │ └── LC_MESSAGES
│ │ ├── django.mo
│ │ └── django.po
├── templates
│ ├── simpleblog
│ │ ├── post_list.html
│ │ ├── post_archive.html
│ │ ├── includes
│ │ │ └── post_form.html
│ │ ├── post_detail_page.html
│ │ ├── post_archive_page.html
│ │ ├── post_list_page.html
│ │ └── post_detail.html
│ └── tags
│ │ └── latest_blog_posts.html
├── __init__.py
├── settings.py
├── signals.py
├── tests.py
├── urls.py
├── admin.py
├── forms.py
├── models.py
└── views.py
├── .gitignore
├── requirements.txt
├── MANIFEST.in
├── TODO
├── LICENSE
├── setup.py
└── README.rst
/simpleblog/migrations/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/simpleblog/templatetags/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | build
3 | dist
4 | *egg-info
5 | .idea
6 |
--------------------------------------------------------------------------------
/simpleblog/locale/fr_FR/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drager/django-simple-blog/HEAD/simpleblog/locale/fr_FR/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/simpleblog/locale/sv_SE/LC_MESSAGES/django.mo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drager/django-simple-blog/HEAD/simpleblog/locale/sv_SE/LC_MESSAGES/django.mo
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | Django>=1.8
2 | django-el-pagination==3.0.1
3 | django-simple-math-captcha>=1.0.3
4 | django-markdown-deux>=1.0.4
5 | django-pagedown>=0.0.5
6 |
--------------------------------------------------------------------------------
/simpleblog/templates/simpleblog/post_list.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
5 | {% include page_template %}
6 |
7 | {% endblock %}
8 |
--------------------------------------------------------------------------------
/simpleblog/__init__.py:
--------------------------------------------------------------------------------
1 | VERSION = (0, 3, 0)
2 |
3 |
4 | def get_version():
5 | """Return the Django Simple Blog version as a string."""
6 | return '.'.join(map(str, VERSION))
7 |
--------------------------------------------------------------------------------
/simpleblog/templates/simpleblog/post_archive.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% block content %}
4 |
7 | {% include page_template %}
8 | {% endblock %}
9 |
--------------------------------------------------------------------------------
/simpleblog/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django Simple Blog settings file.
3 | """
4 |
5 | from django.conf import settings
6 |
7 | # How long the length of the textarea should be.
8 | MAX_LENGTH_TEXTAREA = getattr(settings, 'BLOG_MAX_LENGTH_TEXTAREA', None)
9 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include LICENSE
2 | include README.rst
3 | include requirements.txt
4 | recursive-include simpleblog/templates *
5 | recursive-include simpleblog/templatetags *
6 | recursive-include simpleblog/locale *
7 | recursive-include simpleblog/migrations *
--------------------------------------------------------------------------------
/simpleblog/signals.py:
--------------------------------------------------------------------------------
1 |
2 |
3 | def save_comment(sender, instance, created, **kwargs):
4 | if created:
5 | comment = instance
6 | post = comment.post
7 | post.comment_count = post.comments.count()
8 | post.save(force_update=True)
9 |
--------------------------------------------------------------------------------
/TODO:
--------------------------------------------------------------------------------
1 | # Create our own tags so rendering posts/comments follows DRY
2 |
3 | # Pagedown: enforce comment max length
4 |
5 | # Pagedown: can we display editor and preview side-by-side? (would look better
6 | in admin dashboard)
7 |
8 | # Pagedown: left-align comment editor
9 |
10 | # Make blog post content (not comments) unsafe so the author can include HTML
11 | tags
12 |
--------------------------------------------------------------------------------
/simpleblog/templates/tags/latest_blog_posts.html:
--------------------------------------------------------------------------------
1 | {% load humanize %}
2 |
3 |
4 |
7 |
8 | {% for latest_blog_post in latest_blog_posts %}
9 |
10 |
11 | {{ latest_blog_post }}
12 |
13 | - {{ latest_blog_post.post_date|naturaltime }}
14 |
15 | {% endfor %}
16 |
17 |
18 |
--------------------------------------------------------------------------------
/simpleblog/tests.py:
--------------------------------------------------------------------------------
1 | """
2 | This file demonstrates writing tests using the unittest module. These will pass
3 | when you run "manage.py test".
4 |
5 | Replace this with more appropriate tests for your application.
6 | """
7 |
8 | from django.test import TestCase
9 |
10 |
11 | class SimpleTest(TestCase):
12 |
13 | def test_basic_addition(self):
14 | """
15 | Tests that 1 + 1 always equals 2.
16 | """
17 | self.assertEqual(1 + 1, 2)
18 |
--------------------------------------------------------------------------------
/simpleblog/templates/simpleblog/includes/post_form.html:
--------------------------------------------------------------------------------
1 | {% comment %}
2 | Ugly but unfortunately short of refactoring I have no way around it.
3 | Open to comments
4 | {% endcomment %}
5 | {{ comment_form.media }}
6 |
7 |
--------------------------------------------------------------------------------
/simpleblog/templatetags/blog_tags.py:
--------------------------------------------------------------------------------
1 | from django import template
2 |
3 | from ..models import Post
4 |
5 | register = template.Library()
6 |
7 |
8 | def latest_blog_posts(context, num):
9 | """
10 | Displays the most recent blog posts. It takes an argument, num
11 | and displays so many posts depending on the value.
12 | """
13 | latest_blog_posts = Post.objects.all()[:num].select_related()
14 |
15 | return {
16 | 'latest_blog_posts': latest_blog_posts
17 | }
18 |
19 | register.inclusion_tag('tags/latest_blog_posts.html',
20 | takes_context=True)(latest_blog_posts)
21 |
--------------------------------------------------------------------------------
/simpleblog/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import re_path
2 |
3 | from .views import BlogDetailView, BlogListView, LatestEntriesFeed
4 |
5 | urlpatterns = [
6 | re_path(r'^(?P\d{4})/(?P\d{2})/(?P\d{2})/(?P[-_\w]+)/$',
7 | BlogDetailView.as_view(),
8 | name='blog_detail',
9 | ),
10 | re_path(r'^archive/$',
11 | BlogListView.as_view(
12 | template_name="simpleblog/post_archive.html",
13 | page_template="simpleblog/post_archive_page.html"),
14 | name="blog_archive"),
15 | re_path(r'^latest/feed/$', LatestEntriesFeed()),
16 | re_path(r'^$', BlogListView.as_view(), name='blog_index'),
17 | ]
18 |
--------------------------------------------------------------------------------
/simpleblog/templates/simpleblog/post_detail_page.html:
--------------------------------------------------------------------------------
1 | {% load el_pagination_tags %}
2 | {% load markdown_deux_tags %}
3 |
4 | {% if comments %}
5 | {% paginate comments %}
6 | {% for comment in comments %}
7 |
21 | {% endfor %}
22 |
23 | {% show_more "Load more comments" %}
24 |
25 | {% else %}
26 | There's no comments...
27 | {% endif %}
28 |
--------------------------------------------------------------------------------
/simpleblog/templates/simpleblog/post_archive_page.html:
--------------------------------------------------------------------------------
1 | {% load el_pagination_tags %}
2 |
3 | {% if posts %}
4 | {% paginate posts %}
5 |
6 | {% for post in posts %}
7 |
8 |
13 |
14 | Posted on
15 |
16 | {{ post.post_date }}
17 |
18 | by
19 | {{ post.posted_by }} -
20 |
21 |
22 | {{ post.comment_count }} comment{{ post.comment_count|pluralize }}
23 |
24 |
25 |
26 |
27 |
28 | {% endfor %}
29 |
30 | {% show_more "Load more blog posts" %}
31 |
32 | {% else %}
33 | There's no blog entries...
34 | {% endif %}
35 |
--------------------------------------------------------------------------------
/simpleblog/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from django.db import models as django_models
3 | from pagedown.widgets import AdminPagedownWidget
4 |
5 | from . import models
6 |
7 |
8 | class PostAdmin(admin.ModelAdmin):
9 | # Note: this makes pagedown the default editor for ALL text fields
10 | formfield_overrides = {
11 | django_models.TextField: {'widget': AdminPagedownWidget },
12 | }
13 | prepopulated_fields = {'slug': ('title',)}
14 | list_display = ('title', 'post_date', 'posted_by',
15 | 'comment_count', 'allow_comments')
16 | readonly_fields = ('comment_count',)
17 |
18 |
19 | class CommentAdmin(admin.ModelAdmin):
20 | # Note: this makes pagedown the default editor for ALL text fields
21 | formfield_overrides = {
22 | django_models.TextField: {'widget': AdminPagedownWidget },
23 | }
24 | list_display = ('user_name', 'user_email', 'ip_address', 'post_date')
25 |
26 | admin.site.register(models.Post, PostAdmin)
27 | admin.site.register(models.Comment, CommentAdmin)
28 |
--------------------------------------------------------------------------------
/simpleblog/templates/simpleblog/post_list_page.html:
--------------------------------------------------------------------------------
1 | {% load el_pagination_tags %}
2 |
3 | {% load markdown_deux_tags %}
4 |
5 | {% if posts %}
6 | {% paginate posts %}
7 |
8 | {% for post in posts %}
9 |
10 |
15 |
16 |
17 | Posted on
18 |
19 | {{ post.post_date }}
20 |
21 | by
22 | {{ post.posted_by }} -
23 |
24 |
25 | {{ post.comment_count }} comment{{ post.comment_count|pluralize }}
26 |
27 |
28 |
29 |
30 | {{ post.bodytext|markdown }}
31 |
32 |
33 | Read more
34 | {{ post.bodytext|wordcount }}
35 |
36 |
37 | {% endfor %}
38 |
39 | {% show_more "Load more blog posts" %}
40 |
41 | {% else %}
42 | There's no blog entries...
43 | {% endif %}
44 |
--------------------------------------------------------------------------------
/simpleblog/templates/simpleblog/post_detail.html:
--------------------------------------------------------------------------------
1 | {% extends 'base.html' %}
2 |
3 | {% load markdown_deux_tags %}
4 |
5 | {% block content %}
6 |
7 |
8 |
13 |
14 |
15 | Posted on
16 |
17 | {{ post.post_date }}
18 |
19 | by
20 | {{ post.posted_by }} -
21 |
22 |
23 | {{ post.comment_count }} comment{{ post.comment_count|pluralize }}
24 |
25 |
26 |
27 |
28 | {{ post.bodytext|markdown }}
29 |
30 |
31 | {{ post.bodytext|wordcount }}
32 |
33 |
34 |
35 |
36 |
37 | Comment
38 | {% if post.allow_comments %}
39 | {% include 'simpleblog/includes/post_form.html' %}
40 |
41 | Comments
42 |
43 | {% include page_template %}
44 |
45 | {% else %}
46 | Comments are disabled for this post.
47 | {% endif %}
48 |
49 | {% endblock %}
50 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 Jesper Håkansson and individual contributors.
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification, are
5 | permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice, this list of
8 | conditions and the following disclaimer.
9 |
10 | 2. Redistributions in binary form must reproduce the above copyright notice, this list
11 | of conditions and the following disclaimer in the documentation and/or other materials
12 | provided with the distribution.
13 |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
21 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from setuptools import setup
4 |
5 | version = __import__('simpleblog').get_version()
6 |
7 | f = open(os.path.join(os.path.dirname(__file__), 'README.rst'))
8 | readme = f.read()
9 | f.close()
10 |
11 |
12 | def read(fname):
13 | try:
14 | return open(os.path.join(os.path.dirname(__file__), fname)).read()
15 | except IOError:
16 | return ''
17 |
18 | setup(
19 | name = 'django-simple-blog',
20 | version = version,
21 | packages = ['simpleblog'],
22 | include_package_data = True,
23 | install_requires = [line for line in read('requirements.txt').split('\n')
24 | if line and not line.startswith('#')],
25 | license = 'BSD License',
26 | description = 'An easy way to start writing blog posts.',
27 | long_description = readme,
28 | url = 'https://github.com/Drager/django-simple-blog',
29 | author = 'Drager',
30 | author_email = 'drager@programmers.se',
31 | classifiers = [
32 | 'Environment :: Web Environment',
33 | 'Framework :: Django',
34 | 'Intended Audience :: Developers',
35 | 'License :: OSI Approved :: BSD License',
36 | 'Programming Language :: Python',
37 | 'Programming Language :: Python :: 2.6',
38 | 'Programming Language :: Python :: 2.7',
39 | 'Programming Language :: Python :: 3.4',
40 | 'Programming Language :: Python :: 3.5',
41 | 'Topic :: Internet :: WWW/HTTP',
42 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
43 | ],
44 | )
45 |
--------------------------------------------------------------------------------
/simpleblog/forms.py:
--------------------------------------------------------------------------------
1 | from random import choice, randint
2 |
3 | from django import forms
4 | from django.utils.safestring import mark_safe
5 | from django.utils.translation import ugettext_lazy as _
6 | from pagedown.widgets import PagedownWidget
7 | from simplemathcaptcha.fields import MathCaptchaField
8 |
9 | from .models import Comment
10 | from .settings import MAX_LENGTH_TEXTAREA
11 |
12 |
13 | class UserCommentForm(forms.ModelForm):
14 | error_msg = _(
15 | 'Cannot be empty nor only contain spaces. Please fill in the field.')
16 |
17 | bodytext = forms.CharField(widget=PagedownWidget())
18 |
19 | class Meta:
20 | model = Comment
21 | fields = ["bodytext"]
22 | if MAX_LENGTH_TEXTAREA is not None:
23 | widgets = {
24 | 'bodytext': forms.Textarea(attrs={'maxlength': MAX_LENGTH_TEXTAREA})
25 | }
26 |
27 | def clean_bodytext(self):
28 | bodytext = self.cleaned_data.get('bodytext')
29 | if bodytext:
30 | if not bodytext.strip():
31 | raise forms.ValidationError(self.error_msg)
32 | return bodytext
33 |
34 |
35 | class CommentForm(UserCommentForm):
36 | user_name = forms.CharField(label=_('Username'), initial=_('anonymous'))
37 | user_email = forms.EmailField(label=_('E-mail'), required=False)
38 | # captcha = MathCaptchaField(
39 | # required=True, error_messages={'invalid': _('Welcome robot')})
40 |
41 | class Meta:
42 | model = Comment
43 | fields = ("user_name", "user_email", "bodytext")
44 | if MAX_LENGTH_TEXTAREA is not None:
45 | widgets = {
46 | 'bodytext': forms.Textarea(attrs={'maxlength': MAX_LENGTH_TEXTAREA})
47 | }
48 |
49 | def clean_user_name(self):
50 | self.error_msg
51 | user_name = self.cleaned_data.get('user_name')
52 | if user_name:
53 | if not user_name.strip():
54 | raise forms.ValidationError(self.error_msg)
55 | return user_name
56 |
--------------------------------------------------------------------------------
/simpleblog/locale/sv_SE/LC_MESSAGES/django.po:
--------------------------------------------------------------------------------
1 | # Swedish translation of this blog.
2 | # Copyright (C) 2014
3 | # This file is distributed under the same license as the django-simple-blog package.
4 | # Jesper Håkansson , 2014.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: PACKAGE VERSION\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2014-02-05 13:14+0100\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: Jesper Håkansson \n"
14 | "Language-Team: Jesper Håkansson , 2017.
5 | #
6 | #, fuzzy
7 | msgid ""
8 | msgstr ""
9 | "Project-Id-Version: PACKAGE VERSION\n"
10 | "Report-Msgid-Bugs-To: \n"
11 | "POT-Creation-Date: 2017-12-10 11:22+0100\n"
12 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 | "Last-Translator: Florimond Manca \n"
14 | "Language-Team: Florimond Manca \n"
15 | "Language: French\n"
16 | "MIME-Version: 1.0\n"
17 | "Content-Type: text/plain; charset=UTF-8\n"
18 | "Content-Transfer-Encoding: 8bit\n"
19 |
20 | #: forms.py:15
21 | msgid "Cannot be empty nor only contain spaces. Please fill in the field."
22 | msgstr "Ce champ ne peut pas être vide ou ne contenir que des espaces, veuillez le remplir."
23 |
24 | #: forms.py:36
25 | msgid "Username"
26 | msgstr "Nom d'utilisateur"
27 |
28 | #: forms.py:36
29 | msgid "anonymous"
30 | msgstr "anonyme"
31 |
32 | #: forms.py:37
33 | msgid "E-mail"
34 | msgstr "Adresse électronique"
35 |
36 | #: forms.py:39
37 | msgid "Welcome robot"
38 | msgstr "Bienvenue, robot"
39 |
40 | #: models.py:15
41 | msgid "title"
42 | msgstr "titre"
43 |
44 | #: models.py:17 models.py:53
45 | msgid "message"
46 | msgstr "contenu"
47 |
48 | #: models.py:20 models.py:56
49 | msgid "post date"
50 | msgstr "publié le"
51 |
52 | #: models.py:21
53 | msgid "modified"
54 | msgstr "modifié le"
55 |
56 | #: models.py:23
57 | msgid "posted by"
58 | msgstr "publié par"
59 |
60 | #: models.py:26
61 | msgid "allow comments"
62 | msgstr "autoriser les commentaires"
63 |
64 | #: models.py:28
65 | msgid "comment count"
66 | msgstr "nombre de commentaires"
67 |
68 | #: models.py:31 models.py:52
69 | msgid "post"
70 | msgstr "billet"
71 |
72 | #: models.py:32
73 | msgid "posts"
74 | msgstr "billets"
75 |
76 | #: models.py:58
77 | msgid "ip address"
78 | msgstr "adresse IP"
79 |
80 | #: models.py:62
81 | msgid "user"
82 | msgstr "utilisateur"
83 |
84 | #: models.py:64
85 | msgid "user name"
86 | msgstr "nom d'utilisateur"
87 |
88 | #: models.py:65
89 | msgid "user email"
90 | msgstr "email utilisateur"
91 |
92 | #: models.py:71
93 | msgid "comment"
94 | msgstr "commentaire"
95 |
96 | #: models.py:72
97 | msgid "comments"
98 | msgstr "commentaires"
99 |
--------------------------------------------------------------------------------
/simpleblog/models.py:
--------------------------------------------------------------------------------
1 | from __future__ import unicode_literals
2 |
3 | from django.conf import settings
4 | from django.urls import reverse
5 | from django.db import models
6 | from django.db.models.signals import post_save
7 | from django.utils.encoding import python_2_unicode_compatible
8 | from django.utils.translation import ugettext_lazy as _
9 |
10 | from .signals import save_comment
11 |
12 |
13 | @python_2_unicode_compatible
14 | class Post(models.Model):
15 | title = models.CharField(max_length=200, verbose_name=_("title"))
16 | slug = models.SlugField()
17 | bodytext = models.TextField(verbose_name=_("message"))
18 |
19 | post_date = models.DateTimeField(
20 | auto_now_add=True, verbose_name=_("post date"))
21 | modified = models.DateTimeField(null=True, verbose_name=_("modified"))
22 | posted_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True,
23 | verbose_name=_("posted by"),
24 | on_delete=models.SET_NULL)
25 |
26 | allow_comments = models.BooleanField(
27 | default=True, verbose_name=_("allow comments"))
28 | comment_count = models.IntegerField(
29 | blank=True, default=0, verbose_name=_('comment count'))
30 |
31 | class Meta:
32 | verbose_name = _('post')
33 | verbose_name_plural = _('posts')
34 | ordering = ['-post_date']
35 |
36 | def __str__(self):
37 | return self.title
38 |
39 | def get_absolute_url(self):
40 | kwargs = {
41 | 'slug': self.slug,
42 | 'year': '%04d' % self.post_date.year,
43 | 'month': '%02d' % self.post_date.month,
44 | 'day': '%02d' % self.post_date.day,
45 | }
46 |
47 | return reverse('blog_detail', kwargs=kwargs)
48 |
49 |
50 | @python_2_unicode_compatible
51 | class Comment(models.Model):
52 | post = models.ForeignKey(
53 | Post, related_name='comments', verbose_name=_("post"),
54 | on_delete=models.CASCADE)
55 | bodytext = models.TextField(verbose_name=_("message"))
56 |
57 | post_date = models.DateTimeField(
58 | auto_now_add=True, verbose_name=_("post date"))
59 | ip_address = models.GenericIPAddressField(
60 | default='0.0.0.0', verbose_name=_("ip address"))
61 |
62 | user = models.ForeignKey(
63 | settings.AUTH_USER_MODEL, null=True, blank=True,
64 | verbose_name=_("user"), related_name='comment_user',
65 | on_delete=models.SET_NULL)
66 | user_name = models.CharField(
67 | max_length=50, default='anonymous', verbose_name=_("user name"))
68 | user_email = models.EmailField(blank=True, verbose_name=_("user email"))
69 |
70 | def __str__(self):
71 | return self.bodytext
72 |
73 | class Meta:
74 | verbose_name = _('comment')
75 | verbose_name_plural = _('comments')
76 | ordering = ['post_date']
77 |
78 |
79 | post_save.connect(save_comment, sender=Comment)
80 |
--------------------------------------------------------------------------------
/simpleblog/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # Generated by Django 1.10.2 on 2016-10-03 11:02
3 | from __future__ import unicode_literals
4 |
5 | import django.db.models.deletion
6 | from django.conf import settings
7 | from django.db import migrations, models
8 |
9 |
10 | class Migration(migrations.Migration):
11 |
12 | initial = True
13 |
14 | dependencies = [
15 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
16 | ]
17 |
18 | operations = [
19 | migrations.CreateModel(
20 | name='Comment',
21 | fields=[
22 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
23 | ('bodytext', models.TextField(verbose_name='message')),
24 | ('post_date', models.DateTimeField(auto_now_add=True, verbose_name='post date')),
25 | ('ip_address', models.GenericIPAddressField(default='0.0.0.0', verbose_name='ip address')),
26 | ('user_name', models.CharField(default='anonymous', max_length=50, verbose_name='user name')),
27 | ('user_email', models.EmailField(blank=True, max_length=254, verbose_name='user email')),
28 | ],
29 | options={
30 | 'ordering': ['post_date'],
31 | 'verbose_name': 'comment',
32 | 'verbose_name_plural': 'comments',
33 | },
34 | ),
35 | migrations.CreateModel(
36 | name='Post',
37 | fields=[
38 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
39 | ('title', models.CharField(max_length=200, verbose_name='title')),
40 | ('slug', models.SlugField()),
41 | ('bodytext', models.TextField(verbose_name='message')),
42 | ('post_date', models.DateTimeField(auto_now_add=True, verbose_name='post date')),
43 | ('modified', models.DateTimeField(null=True, verbose_name='modified')),
44 | ('allow_comments', models.BooleanField(default=True, verbose_name='allow comments')),
45 | ('comment_count', models.IntegerField(blank=True, default=0, verbose_name='comment count')),
46 | ('posted_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='posted by')),
47 | ],
48 | options={
49 | 'ordering': ['-post_date'],
50 | 'verbose_name': 'post',
51 | 'verbose_name_plural': 'posts',
52 | },
53 | ),
54 | migrations.AddField(
55 | model_name='comment',
56 | name='post',
57 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='simpleblog.Post', verbose_name='post'),
58 | ),
59 | migrations.AddField(
60 | model_name='comment',
61 | name='user',
62 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='comment_user', to=settings.AUTH_USER_MODEL, verbose_name='user'),
63 | ),
64 | ]
65 |
--------------------------------------------------------------------------------
/simpleblog/views.py:
--------------------------------------------------------------------------------
1 | from django.contrib.sites.models import Site
2 | from django.contrib.syndication.views import Feed
3 | from django.shortcuts import redirect
4 | from django.views.generic.dates import DateDetailView
5 | from el_pagination.views import AjaxListView
6 |
7 | from .forms import CommentForm, UserCommentForm
8 | from .models import Comment, Post
9 |
10 |
11 | class LatestEntriesFeed(Feed):
12 | title = "%s blog entries" % (Site.objects.get_current())
13 | description = "The latest blog entries"
14 | link = "/siteposts/"
15 |
16 | def items(self):
17 | return Post.objects.order_by('-post_date')[:5]
18 |
19 | def item_title(self, item):
20 | return item.title
21 |
22 | def item_description(self, item):
23 | return item.bodytext
24 |
25 |
26 | class BlogListView(AjaxListView):
27 | context_object_name = "posts"
28 | queryset = Post.objects.all().select_related()
29 |
30 |
31 | class BlogDetailView(DateDetailView):
32 | model = Post
33 | date_field = 'post_date'
34 | month_format = '%m'
35 | page_template = "simpleblog/post_detail_page.html"
36 |
37 | def get_queryset(self):
38 | queryset = super(BlogDetailView, self).get_queryset()
39 | return queryset.select_related()
40 |
41 | def post(self, request, *args, **kwargs):
42 | self.object = post = self.get_object()
43 | if request.user.is_authenticated:
44 | form = UserCommentForm(request.POST)
45 | else:
46 | form = CommentForm(request.POST)
47 | if form.is_valid():
48 | comment = form.save(commit=False)
49 | comment.post = post
50 | if request.user.is_authenticated:
51 | comment.user = request.user
52 | comment.user_name = request.user
53 | comment.user_email = request.user.email
54 | comment.ip = '0.0.0.0'
55 | comment.save()
56 | return redirect(post.get_absolute_url())
57 | context = self.get_context_data(object=post)
58 | context['comment_form'] = form
59 | return self.render_to_response(context)
60 |
61 | def get_context_data(self, **kwargs):
62 | if self.request.user.is_authenticated:
63 | form = UserCommentForm()
64 | else:
65 | form = CommentForm()
66 | context = {
67 | 'page_template': self.page_template,
68 | 'comment_form': form,
69 | 'comments': Comment.objects.filter(post=self.object.id).select_related()
70 | }
71 | return super(BlogDetailView, self).get_context_data(**context)
72 |
73 | def render_to_response(self, context, **response_kwargs):
74 | """
75 | Returns a response with a template depending if the request is ajax
76 | or not and it renders with the given context.
77 | """
78 | if self.request.is_ajax():
79 | template = self.page_template
80 | else:
81 | template = self.get_template_names()
82 | return self.response_class(
83 | request=self.request,
84 | template=template,
85 | context=context,
86 | **response_kwargs
87 | )
88 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | ==========================
2 | django-simple-blog
3 | ==========================
4 |
5 | What is it?
6 | ===========
7 |
8 | django-simple-blog is a simple Django app for fast integrating your
9 | current project with a blog-system
10 |
11 | You can easily write blog posts, let users comment the posts if you'd like to.
12 |
13 | Installation
14 | ============
15 |
16 | You can do any of the following to install ``django-simple-blog``
17 |
18 | - Run ``pip install django-simple-blog``.
19 | - Run ``easy_install django-simple-blog``.
20 | - Download or "git clone" the package and run ``setup.py``.
21 | - Download or "git clone" the package and add ``simpleblog`` to your PYTHONPATH.
22 |
23 |
24 | Usage
25 | =====
26 |
27 | 1. Add ``simpleblog`` and `django.contrib.sites` to your INSTALLED_APPS setting like this::
28 |
29 | INSTALLED_APPS = [
30 | ...
31 | 'django.contrib.sites',
32 | 'simpleblog',
33 | ]
34 |
35 |
36 | Note: if you want to customize the templates, please add ``el_pagination`` ``markdown_deux`` ``pagedown`` to your INSTALLED_APPS setting.
37 |
38 | INSTALLED_APPS = [
39 | ...
40 | 'django.contrib.sites',
41 | 'el_pagination',
42 | 'markdown_deux',
43 | 'pagedown',
44 | 'simpleblog',
45 | ]
46 | 2. Add `SITE_ID = 1` in your settings.py.
47 | 3. Run ``python manage.py migrate``
48 | 4. Include the ``simpleblog urls`` like this to have your "home page" as the blog index::
49 |
50 | ...
51 |
52 | urlpatterns =[
53 | path('admin/', admin.site.urls),
54 | path('blog/', include('simpleblog.urls')),
55 | ]
56 |
57 | Settings
58 | ========
59 | ``django-simple-blog`` has one setting at the moment::
60 |
61 | # How long the length of the textarea should be.
62 |
63 | MAX_LENGTH_TEXTAREA = 120 #(defaults to None)
64 |
65 |
66 | Templatetags
67 | ===========
68 |
69 | ``django-simple-blog`` comes with one templatetag for getting
70 | the latest desired number of posts. Just do this in any template::
71 | {% load blog_tags %}
72 |
73 | {% latest_blog_posts 5 %}
74 |
75 |
76 | Translation
77 | ===========
78 |
79 | ``django-simple-blog`` is available in ``english``, ``swedish`` and ``french``
80 | at the moment, feel free to translate the application in another
81 | language.
82 |
83 | Admin
84 | =====
85 | For writing posts we will use django's admin application.
86 |
87 | The templates
88 | =============
89 |
90 | The templates is just there as examples for how your templates
91 | could look like, but they work excellent as well, but if you don't
92 | like them, just override them with your own templates simply.
93 |
94 | Requirements
95 | ============
96 |
97 | `Django>=1.8
98 | `_
99 |
100 | `django-el-paginatio>=2.0
101 | `_
102 |
103 | `simplemathcaptcha>=1.0.3
104 | `_
105 |
106 | `django-markdown-deux>=1.0.4
107 | `_
108 |
109 | `django-pagedown>=0.0.5
110 | `_
111 |
112 | If you have problem getting the right versions of these packages,
113 | clone them from their github repository.
114 |
--------------------------------------------------------------------------------
19 |